使用.toSet设置的类型推断失败?

时间:2011-04-04 21:26:30

标签: scala type-inference

为什么类型推断失败?

scala> val xs = List(1, 2, 3, 3)
xs: List[Int] = List(1, 2, 3, 3)

scala> xs.toSet map(_*2)
<console>:9: error: missing parameter type for expanded function ((x$1) => x$1.$times(2))
       xs.toSet map(_*2)

但是,如果分配了xs.toSet,则会进行编译。

scala> xs.toSet
res42: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

scala> res42 map (_*2)
res43: scala.collection.immutable.Set[Int] = Set(2, 4, 6)

另外,从另一方面来说,从Set转换为List,并在List上进行映射符合。

scala> Set(5, 6, 7)
res44: scala.collection.immutable.Set[Int] = Set(5, 6, 7)

scala> res44.toList map(_*2)
res45: List[Int] = List(10, 12, 14)

3 个答案:

答案 0 :(得分:21)

问:为什么toSet没有做我想做的事?

答:那太简单了。

问:但为什么不编译呢? List(1).toSet.map(x => ...)

答:Scala编译器无法推断xInt

问:什么,这是愚蠢的吗?

答:好的,List[A].toSet不会返回immutable.Set[A]。对于某些未知的immutable.Set[B],它会返回B >: A

问:我怎么知道这个?

答:来自Scaladoc。

问:但为什么toSet以这种方式定义?

答:您可能认为immutable.Set是协变的,但事实并非如此。它是不变的。并且toSet的返回类型处于协变位置,因此不能允许返回类型不变。

问:你是什么意思,“协变职位”?

答:让我为你维基百科:http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)。另见Odersky,Venners&amp; Co.的第19章。匙。

问:我现在明白了。但为什么不可变。保持不变?

答:请给我Stack Overflow:Why is Scala's immutable Set not covariant in its type?

问:我投降了。如何修复原始代码?

答:这有效:List(1).toSet[Int].map(x => ...)。这样做:List(1).toSet.map((x: Int) => ...)

(向Friedman&amp; Felleisen道歉。请向paulp&amp; ijuma求助)

编辑: Adriaan's answer以及评论中的讨论中都有宝贵的补充信息。

答案 1 :(得分:15)

由于List#toSet的签名是

,因此类型推断无法正常工作
def toSet[B >: A] => scala.collection.immutable.Set[B]

并且编译器需要在调用中的两个位置推断出类型。在函数中注释参数的另一种方法是使用显式类型参数调用toSet

xs.toSet[Int] map (_*2)

更新

关于你的问题为什么编译器可以分两步推断它,让我们看一下你逐个输入行时会发生什么:

scala> val xs = List(1,2,3)
xs: List[Int] = List(1, 2, 3)

scala> val ys = xs.toSet   
ys: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

在这种情况下,编译器将推断ys的最具体类型,即Set[Int]。此类型现在已知,因此可以推断传递给map的函数的类型。

如果您在示例中填写了所有可能的类型参数,则调用将写为:

xs.toSet[Int].map[Int,Set[Int]](_*2)

其中第二个类型参数用于指定返回集合的类型(有关详细信息,请查看如何实现Scala集合)。这意味着我甚至低估了编译器必须推断出的类型数量。

在这种情况下,推断Int似乎很容易,但有些情况并非如此(考虑到Scala的其他功能,如隐式转换,单例类型,特征作为mixins等)。我不是说它无法完成 - 只是Scala编译器没有这样做。

答案 2 :(得分:13)

我同意推断出“唯一可能”的类型会很好,即使是通过链接进行调用,但也存在技术限制。

您可以将推理视为对表达式的广度优先扫描,在类型变量上收集约束(由子类型边界和必需的隐式参数引起),然后解决这些约束。该方法允许例如暗示引导类型推断。在您的示例中,即使只有一个解决方案,如果您只查看xs.toSet子表达式,以后链接的调用可能会引入使系统不可满足的约束。保持类型变量未解决的缺点是闭包的类型推断需要知道目标类型,因此会失败(它需要具体的东西 - 闭包所需的类型及其参数类型的类型必须不是两个都不知道)。

现在,当延迟解决约束使得推理失败时,我们可以回溯,解决所有类型变量,然后重试,但这很难实现(并且效率可能非常低)。