在Scala 2中,类型推理在使用.toSet?

hsgswve4  于 2022-11-09  发布在  Scala
关注(0)|答案(3)|浏览(199)

为什么类型推断在这里失败了?

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)

此外,反过来,从List转换为Set,并在List上进行Map也符合要求。

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)
q3aa0525

q3aa05251#

问:为什么toSet不能做我想做的事情?
答:那太容易了。
问:但为什么这个不能编译?List(1).toSet.map(x => ...)
答:Scala编译器无法推断xInt
问:什么,这是愚蠢的吗?
答:嗯,List[A].toSet不返回immutable.Set[A]。对于某些未知的B >: A,它返回immutable.Set[B]
问:我怎么会知道呢?
答:来自Scaladoc。
问:但为什么toSet是这样定义的?
答:你可能会认为immutable.Set是协变的,但它不是。它是不变的。并且toSet的返回类型处于协变位置,因此不允许返回类型是不变的。
问:你说的“协变立场”是什么意思?
答:让我帮你上维基百科:http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)。参见《奥德斯基,凡纳斯和勺子》第19章。
问:我现在明白了。但是为什么是一成不变的呢?设置不变量?
A:让我为您堆叠溢出:Why is Scala's immutable Set not covariant in its type?
问:我投降。我如何修复我的原始代码?
答:这行得通:List(1).toSet[Int].map(x => ...)。也是如此:List(1).toSet.map((x: Int) => ...)
(向Friedman&Felleisen道歉。THX向Paulp&Ijua寻求帮助)

**编辑:**在Adriaan's answer和在那里和这里的评论中的讨论中有宝贵的附加信息。

iqjalb3h

iqjalb3h2#

类型推断不能正常工作,因为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的其他特性,如隐式转换、单例类型、混合特性等)。我并不是说不能这样做--只是Scala编译器不能这样做。

lx0bsm1f

lx0bsm1f3#

我同意,即使在调用是连锁的情况下,推断“唯一可能的”类型也是很好的,但存在技术限制。
您可以将推理看作是对表达式的广度优先扫描,收集类型变量上的约束(从子类型边界和必需的隐式参数中产生),然后求解这些约束。该方法允许例如隐含以引导类型推理。在您的示例中,即使只有一个解决方案,如果您只查看xs.toSet子表达式,以后的链接调用可能会引入使系统无法满足的约束。不解决类型变量的缺点是闭包的类型推断要求目标类型是已知的,因此将失败(它需要一些具体的东西来继续--闭包的所需类型及其参数类型不能都是未知的)。
现在,当延迟求解约束导致推理失败时,我们可以回溯,求解所有类型变量,然后重试,但这很难实现(而且可能非常低效)。

相关问题