9

考虑一个定义两个属性的抽象类

abstract class A {
  def a: Int
  def b: Int
  // real A has additional members
}

这是各种案例类的基类,例如

case class Foo(a: Int, b: Int) extends A
case class Bar(a: Int, b: Int) extends A
// and many more

目标:我最终希望能够以两种方式创建上述案例类的实例,即

val b1 = Bar(1, 2)
val b2 = Bar(1) has 2
assert(b1 == b2) // must hold

方法:因此定义一个帮助类来定义has并允许我部分构造As似乎是合理的

case class PartialA(f: Int => A) {
  def has(b: Int) = f(b)
}

问题:当前的机器不允许像Bar(1)这样的调用,因为这实际上是对 的调用Bar.apply(1),即apply由编译器生成的对象定义的方法的调用Bar

如果我可以强制编译器将Bar对象object Bar extends PartialAConstructor生成为

abstract class PartialAConstructor{
  def apply(a: Int, b: Int): A // abstract, created when the compiler creates
                               // object Bar
  def apply(a: Int) = PartialA((b: Int) => apply(a, b))
}

但是,似乎不可能影响案例类的伴随对象的生成。


所需属性:

  • 案例类:FooBar应该保留案例类,因为我想使用编译器生成的好东西,例如结构相等copy和自动生成的提取器。

  • “完全”结构平等:将案例类定义为

    case class Bar(a: Int)(val b: Int)
    

    不是一个选项,因为编译器生成的equals方法只考虑第一个参数列表,因此以下内容将错误地成立:

    assert(Foo(1)(0) == Foo(1)(10))
    
  • 尽可能少的代码重复:例如,当然可以定义一个

    def Bar(a: Int) = PartialA((b: Int) => Bar(a, b))
    

    但是对于扩展的每个案例类都必须这样做A,即 isFooBar

4

3 回答 3

3

您可以严重依赖 currying(以及事实上Foo.apply,作为任何方法,都会自动提升为函数)和一个小助手来增强语法:

object partially {
  def apply[A1,A2,R]( f: (A1, A2) => R ) = f.curried
  def apply[A1,A2,R]( f: (A1, A2) => R, a1: A1 ) = f.curried( a1 )

  def apply[A1,A2,A3,R]( f: (A1, A2, A3) => R ) = f.curried
  def apply[A1,A2,A3,R]( f: (A1, A2, A3) => R, a1: A1 ) = f.curried( a1 )
  def apply[A1,A2,A3,R]( f: (A1, A2, A3) => R, a1: A1, a2: A2 ) = f.curried( a1 )( a2 )


  def apply[A1,A2,A3,A4,R]( f: (A1, A2, A3, A4) => R ) = f.curried
  def apply[A1,A2,A3,A4,R]( f: (A1, A2, A3, A4) => R, a1: A1 ) = f.curried( a1 )
  def apply[A1,A2,A3,A4,R]( f: (A1, A2, A3, A4) => R, a1: A1, a2: A2 ) = f.curried( a1 )( a2 )
  def apply[A1,A2,A3,A4,R]( f: (A1, A2, A3, A4) => R, a1: A1, a2: A2, a3: A3 ) = f.curried( a1 )( a2 )( a3 )
  // ... and so on, potentially up to 22 args
}

然后你可以这样做:

scala> val x = partially(Foo)(1)
x: Int => Foo = <function1>
scala> x(2)
res37: Foo = Foo(1,2)

如果您真的想使用您的has方法(而不仅仅是直接应用该函数),请在此之上添加一个隐式类:

implicit class Func1Ops[-A,+R]( val f: A => R ) extends AnyVal { 
  def has( arg: A ): R = f( arg ) 
}

现在你可以这样做:

scala> val x = partially(Foo)(1)
x: Int => Foo = <function1>

scala> x has 2
res38: Foo = Foo(1,2)
于 2013-02-05T14:01:56.723 回答
2

有什么问题

val x = Foo(1, _: Int)

您还可以向伴侣添加一个apply方法,该方法仅需要 1 个 arg 并为您执行部分应用程序。

除此之外,也许还有一种方法可以使用尚未发布的类型宏,但您可以在宏天堂中使用它们。

编辑

要向案例类伴侣添加一些东西,只需像往常一样做:

case class Foo(x: Int, y: Int)

object Foo {
  def apply(x: Int): (Int => Foo) = Foo(x, _: Int)
}

scala> Foo(1,2)
res3: Foo = Foo(1,2)

scala> Foo(1)
res4: Int => Foo = <function1>

在申请中,您还可以返回您的 PartialA 或任何您喜欢的东西。

于 2013-02-05T13:17:57.200 回答
1

假设您真的想要“拥有”DSL,并且可能希望它可扩展,那么以下方法也可以:

abstract class A {
  def a: Int
  def b: Int
}    

trait PartialHas[T] { 
    self: { def apply(a: Int, b: Int): T } => 
    trait HasWord { def has(b: Int): T }
    def apply(a: Int): HasWord = new HasWord { def has(b: Int): T = apply(a, b) } 
}    

case class Bar(a: Int, b: Int) extends A
object Bar extends PartialHas[Bar]

可能有一种方法可以使用类宏来完全取消显式伴随定义。

于 2014-03-20T18:38:57.250 回答