如何使用多个参数列表消除案例类创建的歧义?

时间:2015-02-20 18:34:16

标签: scala currying

我有一个看起来像这样的案例类:

case class A(first: B*)(second: C*)

重复了firstsecond,因此我将其放在单独的参数列表中。但是,我希望在很多情况下second可能是空的,因此能够使用像A(???, ???)这样的类而不用尾随空括号会很好。我尝试了以下方法:

case class A(first: B*)(second: C*) {
  def this(first: B*) = this(first: _*)()
}

这给了我ambiguous reference to overloaded definition

有没有办法明确地编写这个构造函数调用? (并且我能够调用重载的构造函数而不会再次混淆语法吗?)我的猜测是否定的,关于这种语法糖会如何破坏currying或其他一些问题的一些争论,但我更喜欢从Scala知识比我更多的人;)

3 个答案:

答案 0 :(得分:3)

以下内容可以实现您的目标:

case class Foo private(first: List[Int], second: List[Int])

object Foo {
    def apply(first: Int*) = new Foo(first.toList, List.empty[Int]) { 
        def apply(second: Int*) = new Foo(first.toList, second.toList)
    }
}

然后你可以这样做:

Foo(1, 2, 3)
Foo(1, 2, 3)(4, 5, 6)

由@SillyFreak编辑:这个变体没有给出结构类型成员的反射访问权限"警告,所以我认为它应该在表现方面更好一点:

case class Foo private (first: List[Int], second: List[Int])

object Foo {
  def apply(first: Int*) = new Foo(first.toList, List.empty[Int]) with NoSecond

  trait NoSecond {
    self: Foo =>
    def apply(second: Int*) = new Foo(first.toList, second.toList)
  }
}

Foo(1, 2, 3)
Foo(1, 2, 3)(4, 5, 6)

答案 1 :(得分:2)

修改 Ben Reich突破了障碍并证明了这实际上是可能的。这比他的内容略有改进,不依赖于reflective calls feature

case class Foo private(first: List[Int], second: List[Int]) {
  def apply(newSecond: Int*) = new Foo(first.toList, newSecond.toList)
}
object Foo {
  def apply(first: Int*) = new Foo(first.toList, List.empty[Int])
}

与他的例子相比,唯一可能的缺点是你可以多次调用Foo个对象,而在他的例子中,你只能对仅用{{1}构造的Foo这样做提供。


从某种意义上说,我不认为这两个选项应该发生冲突。这是因为您不能仅使用其中一个参数列表直接调用多参数列表方法。两个参数列表都是方法调用的组成部分,从技术上讲,您必须使用post-fix first运算符来部分应用第一个参数列表并获取一个带有第二个参数列表的函数(序列,而不是varargs,你可能会注意到)。在某些情况下,这会自动发生,但并非总是如此。因此,除非您明确使用_,否则编译器应该能够通过总是假设执行完整的方法调用来区分这两个选项。

但是Scala中的重载有点不确定,通常是由于JVM表示转换的实现细节。或者也许只是因为调用方法与函数之间没有语法差异,并且允许这样做的分析根本就不存在。

但是,当您尝试调用重载方法时,这不起作用。以下是几种变体:

重载_方法

apply

重载的构造函数

(原始问题的例子)

拆分构造函数和scala> :paste // Entering paste mode (ctrl-D to finish) case class A(first: Int*)(second: Int*) object A { def apply(first: Int*) = new A(first: _*)() } // Exiting paste mode, now interpreting. defined class A defined object A scala> A(1,2,3) <console>:12: error: ambiguous reference to overloaded definition, both method apply in object A of type (first: Int*)(second: Int*)A and method apply in object A of type (first: Int*)A match argument types (Int,Int,Int) A(1,2,3) ^

apply

因此,无论您是尝试使用scala> :paste // Entering paste mode (ctrl-D to finish) class A(first: Int*)(second: Int*) object A { def apply(first: Int*) = new A(first: _*)() } // Exiting paste mode, now interpreting. defined class A defined object A scala> A(5, 6, 7) res5: A = A@47a36ea0 scala> A(5, 6, 7)(4, 5) <console>:12: error: A does not take parameters A(5, 6, 7)(4, 5) ^ scala> new A(5, 6, 7)(4, 5) res7: A = A@62a75ec scala> new A(5, 6, 7) <console>:10: error: missing arguments for constructor A in class A new A(5, 6, 7) ^ 还是使用构造函数来获取此重载行为,您都会遇到歧义问题。正如你所看到的,将它作为一个常规类(不定义默认的apply)并拆分方法是有效的,但我很确定它没有达到你想要的优雅。

答案 2 :(得分:1)

案例类已经生成了apply的实现(在生成的伴随对象中),这就是为什么你可以val a = A()()而不是val a = new A()()

您需要做的就是编写自己的apply实现,以实现您的目标:

object A {
  def apply(first: B*): A = new A(first: _ *)()
}

这是一种用于编写替代&#34;构造函数的有用的通用技术。这是伴随对象中真正的工厂方法。