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

Adr*_*ian 26 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)
Run Code Online (Sandbox Code Playgroud)

但是,如果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)
Run Code Online (Sandbox Code Playgroud)

此外,走另一条路,转换为SetList,并映射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)
Run Code Online (Sandbox Code Playgroud)

Set*_*sue 21

问:为什么toSet不做我想要的?

答:那太简单了.

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

答:Scala编译器无法推断出这x是一个Int.

问:什么,这是愚蠢的?

A:好吧,List[A].toSet不回复immutable.Set[A].它返回immutable.Set[B]一些未知的B >: A.

问:我怎么知道这个?

答:来自斯卡拉多克.

问:但为什么这样toSet定义?

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

问:你的意思是什么,"协变位置"?

A:让我为你维基百科:http://en.wikipedia.org/wiki/Covariance_and_contravariance_ (computer_science).另见Odersky,Venners&Spoon的第19章.

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

A:让我为您解决Stack Overflow:为什么Scala的不可变Set在其类型中不协变?

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

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

(向Friedman&Felleisen道歉.请向paulp&ijuma求助)

编辑:Adriaan的答案和评论中的讨论中都有有价值的补充信息.

  • 问:为什么你给我一个负面评价? - 答:我知道你的答案听起来很聪明和讽刺...... - 问:是的!你通过我看到了! - 答:我知道.但对我来说,这太傲慢了. - 问:哦,你是对的.既然你这么说,我其实是傲慢的. - 答:你的回答让原始的海报看起来很愚蠢,把愚蠢的话放在嘴里. - 问:你是对的.这对我来说实际上是非常愚蠢的. - 答:我们可以从一些文档推断StackOverflow上的几乎所有内容.你会因此认为StackOverflow没用吗? - 答:不.也许有时候问更好. (8认同)
  • @Seth Tisue这很棒但是Scala编译器能够推断出x是一个Int.我们可以看到:scala> xs.toSet res42:scala.collection.immutable.Set [Int] = Set(1,2,3)它创建一个Int集合.为什么不能在第一种情况下推断它呢? (2认同)

Mor*_*itz 15

该类型推断不正确的签名的工作List#toSet

def toSet[B >: A] => scala.collection.immutable.Set[B]
Run Code Online (Sandbox Code Playgroud)

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

xs.toSet[Int] map (_*2)
Run Code Online (Sandbox Code Playgroud)

更新:

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

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)
Run Code Online (Sandbox Code Playgroud)

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

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

xs.toSet[Int].map[Int,Set[Int]](_*2)
Run Code Online (Sandbox Code Playgroud)

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

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


Adr*_*ors 13

我同意推断"唯一可能的"类型会很好,即使在链接被调用时,也存在技术限制.

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

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

  • 我忘了添加:您可以使用`-Ytyper-debug`命令行选项查看操作中的类型推断.我强烈建议首先将您的示例减少到最低限度,否则您将很快迷失在大量的调试输出中.我们正在开发一种可视化此信息的工具. (2认同)
  • 那么......这只是实施中的一个不足之处,可能会在某些时候消失?我仍然不明白添加哪些约束使一切都不可满足,特别是当两步示例有效时.(我没有从其他两个答案得到任何具体内容,当进一步按下时,答案者也在评论中说,他们并不完全了解幕后发生的事情.) (2认同)