如何使用多个参数列表消除 case class 创建的歧义?

How to disambiguate case class creation with multiple parameter lists?

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

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

firstsecond都是重复的,所以我把它们放在单独的参数列表中。但是,我预计 second 在很多情况下可能为空,因此能够像 A(???, ???) 一样使用 class 而没有尾随空括号会很好。我尝试了以下方法:

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

这给了我 ambiguous reference to overloaded definition

有没有办法明确地编写这个构造函数调用? (我是否能够调用重载的构造函数而不会再次弄乱语法?)我的猜测是不,有一些关于这种语法糖如何破坏柯里化或类似的争论,但我更愿意从有Scala 知识比我多 ;)

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

您需要做的就是编写自己的 apply 实现来满足您的需求:

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

这是一种有用的通用技术,用于编写替代方案 "constructors",它们实际上是伴随对象中的工厂方法。

编辑 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 对象,而在他的示例中,您只能对仅使用 [= 构造的 Foo 这样做15=] 提供。


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

但是在 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 还是使用构造函数来获得这种重载行为,您都会遇到同样的歧义问题。如您所见,将其设为常规 class(不定义默认值 apply)并拆分方法是可行的,但我很确定它没有达到您正在寻找的优雅.

以下可能会实现您的目标:

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 编辑:这个变体没有给出 "reflective access of structural type member" 警告,所以我认为它应该在性能方面更好一些:

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)