【问题标题】:error: polymorphic expression with default arguments错误:具有默认参数的多态表达式
【发布时间】:2012-10-18 01:26:27
【问题描述】:

这让我很不爽:

trait Foo[A]
class Bar[A](set: Set[Foo[A]] = Set.empty)

这会产生

<console>:8: error: polymorphic expression cannot be instantiated to expected type;
 found   : [A]scala.collection.immutable.Set[A]
 required: Set[Foo[?]]
       class Bar[A](set: Set[Foo[A]] = Set.empty)
                                           ^

我不得不重复Set.empty中的类型参数,这很烦人。为什么使用此默认参数进行类型推断会失败?以下作品:

class Bar[A](set: Set[Foo[A]] = { Set.empty: Set[Foo[A]] })

请注意,这与Set 无关:

case class Hallo[A]()
class Bar[A](hallo: Hallo[A] = Hallo.apply)  // nope

奇怪的是,不仅如此:

class Bar[A](hallo: Hallo[A] = Hallo.apply[A])

...还有这个:

class Bar[A](hallo: Hallo[A] = Hallo())      // ???

【问题讨论】:

  • 不是答案,但需要注意三件事:您可能希望将类型参数命名为 A 以外的名称,以避免与 found: [A]scala.collection.immutable.Set[A] 消息中的(不同)A 混淆;关于Set 和你的Hallo 的重要事实是它们是不变的(而不是说List);并且您的最后一行编译 put 可能无法满足您的要求。
  • class Bar[ A ]( hallo: Hallo[ A ] = Hallo.apply ) 如果您将其更改为使用 Hallo.apply() 它工作正常。你应该可以去掉括号,所以这里一定很混乱。它认为您正在传递部分应用的函数Hallo.apply,而不是在没有参数的情况下调用apply。 (错误消息说它找到了类型[A]()Hallo[A]。)

标签: scala default-value type-inference


【解决方案1】:

您可以直接在empty 方法上指定类型,而不必添加额外的一组括号/大括号和类型注释:

class Bar[A]( set: Set[Foo[A]] = Set.empty[Foo[A]] )

至于类型推断失败的原因,请看这些问题:

更新:

抱歉,我仓促的回答太离谱了。上面帖子中的问题与此问题无关。 @TravisBrown 在上面的评论中提出了一个非常好的观点。起初这似乎有效:

class Bar[A]( set: Set[A] = Set.empty )

但如果你真的尝试调用构造函数,它会在使用站点失败:

new Bar[Int]
//  <console>:9: error: type mismatch;
//   found   : scala.collection.immutable.Set[Nothing]
//   required: Set[Int]
//  Note: Nothing <: Int, but trait Set is invariant in type A.
//  You may wish to investigate a wildcard type such as `_ <: Int`. (SLS 3.2.10)
//  Error occurred in an application involving default arguments.
//                new Bar[Int]

这表明编译器不会强制默认参数对所有A 有效,仅对某些A 有效。他们可能做出了这样的选择,因此您可以执行以下操作:

scala> case class MyClass[T](set: Set[T] = Set(0))
defined class MyClass

scala> MyClass() // defaults to MyClass[Int]
res0: MyClass[Int] = MyClass(Set(0))

scala> MyClass(Set('x)) // but I can still use other types manually
res1: MyClass[Symbol] = MyClass(Set('x))

但是,任何类型的参数化类型的嵌套都无法在构造函数的声明站点进行类型检查:

class Bar[A]( set: Set[Option[A]] = Set.empty )
// <console>:7: error: polymorphic expression cannot be instantiated to expected type;
//  found   : [A]scala.collection.immutable.Set[A]
//  required: Set[Option[?]]
//        class Bar[A]( set: Set[Option[A]] = Set.empty )

如果类型参数处于协变位置,则推理不会失败:

class Bar[ A ]( set: List[Foo[A]] = List.empty ) // OK

class Bar[ A ]( set: Map[Int,Foo[A]] = Map.empty ) // OK (unless you use it)

class Bar[ A ]( set: Map[Foo[A],Int] = Map.empty ) // BAD
// <console>:8: error: polymorphic expression cannot be instantiated to expected type;
//  found   : [A, B]scala.collection.immutable.Map[A,B]
//  required: Map[Foo[?],Int]
//            class Bar[ A ]( set: Map[Foo[A],Int] = Map.empty ) // BAD
//                                                       ^

这些是有效的,因为编译器默认选择Nothing 作为协变类型。这适用于List,但如果您真的尝试调用它,上面的第二个示例将不起作用。

造成这种怪异的主要原因可能是 Scala 处理默认参数的方式。编译器会自动向伴生对象添加一个额外的方法,然后无论您遗漏哪个参数,编译器都会自动向伴生对象中的新方法添加一个方法调用,以生成缺少的参数。看起来将默认参数抽象到一个方法中会破坏类型推断中的一些东西,这些东西可以用于正常的赋值。

我认为这些发现中的大多数都非常令人困惑。我从中得出的结论是,实际测试您的默认参数以确保它们在您尝试使用它们时不会破坏类型正确性非常重要!

【讨论】:

  • 是的,我知道empty 的类型参数;我只是想表明强制转换完全解析了类型,这与默认参数的预期类型不同。这对我来说没有意义。此外,我认为这与您链接到的 toSet 的问题无关。
  • 你是对的,在我发布我的答案后,我意识到这与我发布的其他两个链接并不完全相同。让我做更多的挖掘......但是,如果您在示例中使用List 而不是Set,它会起作用,我认为它是相关的。
  • @0__ - 我对我的答案做了一些相当大的修改,所以你可能想再读一遍。我仍然不确定我是否已经回答了您最初的问题——我可能只是添加了更多问题。
  • 您的猜测似乎是对的“这表明编译器不会强制默认参数对所有 A 有效,仅对某些 A 有效。”事实上,SLS §4.6 指出,对于“x: T = e”,“e 使用预期类型 T' 进行类型检查,该类型是通过将 T 中函数类型参数的所有出现替换为未定义类型 (!!) 而获得的。 "它遵循与您类似的示例:def compare[T](a: T = 0)(b: T = a) = (a == b),它允许推断Int。我仍然认为将Set.empty 视为Set.empty[A] 而不是Set.empty[_] 是明智的(并且不违反SLS)
猜你喜欢
  • 2012-03-06
  • 1970-01-01
  • 2016-05-18
  • 2017-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多