Scala 2.10中的Option.fold

4
在使用Scala 2.10.0-M7的下一个会话中:
scala> trait A
defined trait A
scala> class B extends A
defined class B
scala> class C extends A
defined class C
scala> Some(0).fold(new B){_=>new C}
<console>:11: error: type mismatch;
 found   : C
 required: B
              Some(0).fold(new B){_=>new C}

我希望编译器能够找到公共的超类型(即A),而不是报错。这是一般类型推断的限制,还是Option.fold定义方式的后果呢?
谢谢。
3个回答

10

这个问题是由Scala类型推断算法和Option.fold的定义方式结合产生的。

Scala的类型推断从左到右进行,这意味着它从最左边的符号开始搜索表达式可能的类型。对于方法参数列表,这意味着通用类型将被绑定到由最左边的参数列表填充的类型:

scala> def meth[A](a1: A, a2: A) = (a1, a2)
meth: [A](a1: A, a2: A)(A, A)

scala> meth(1, "")
res7: (Any, Any) = (1,"")

scala> def meth[A](a1: A)(a2: A) = (a1, a2)
meth: [A](a1: A)(a2: A)(A, A)

scala> meth(1)("")
<console>:10: error: type mismatch;
 found   : String("")
 required: Int
              meth(1)("")
                      ^

可以看到,第一种情况中推断出的类型为Any,而在第二种情况中,由于A的类型受第一个参数列表的限制,第二个参数列表无法改变它,因此会抛出编译器错误。

但是为了使问题中的方法调用起作用,得到的Option类型可能要等到第二个参数列表才能定义。由于这需要从右到左进行类型推断,因此会出现错误。这与List.fold有些相似:

scala> List(1).foldLeft(Nil)((xs,x) => x::xs)
<console>:8: error: type mismatch;
 found   : List[Int]
 required: scala.collection.immutable.Nil.type
              List(1).foldLeft(Nil)((xs,x) => x::xs)
                                               ^
为了让代码正常工作,需要明确指定结果集合的类型,请参考@rks答案中的示例。
完整的解释请看这里。简而言之,Option在很多方面都遵循集合的设计原则,因此当它像集合一样行为时,更加清晰明了。

1
"Scala的类型推断从左到右进行",这是什么意思? - rks
@rks:我编辑了我的答案。希望现在能解释清楚这个行为了。 - kiritsuku
是的,这更清晰了,谢谢!不过你知道为什么要这样做吗?为什么不在获取所有参数类型后尝试统一(然后在那个时候找到一个公共超类型)? - rks
@rks,是的,我现在已经包含了链接。我不知道类型推断完成的确切原因。它与子类型有很多关系。请参见此[博客文章](http://pchiusano.blogspot.de/2011/05/making-most-of-scalas-extremely-limited.html)(以及评论)以获取有关此问题的讨论 - kiritsuku
是的,我认为这与子类型有关。顺便感谢您提供的链接,我会看一下! - rks

2
这是类型推断算法的一个普遍限制。列表折叠也有同样的限制。引用自Programming in Scala

请注意,flatten 的两个版本都需要在 fold 的起始值——空列表上进行类型注释。这是由于 Scala 类型推断器的限制,它无法自动推断列表的正确类型。

Scala 的类型推断算法在具有多个参数列表的方法之间逐步工作。第一个参数列表中指定的类型可以用于第二个参数列表的推断,第二个参数列表中的类型可以用于第三个参数列表的推断,以此类推。正如样式指南所概述的那样,这允许您在 fold 函数中使用更简单的语法,因为推断引擎从第一个参数列表中知道列表和累加器的类型。
然而,由于它在连续的参数列表中逐步工作,推断引擎不会回过头来更新返回类型(与累加器类型相同),在推断出 fold 函数的函数参数类型后。取而代之的是,您会得到一个类型错误。
在上面的例子中,只需在累加器值上给出类型注释,你就可以了:
Some(0).fold(new B: A){_=>new C}

2
我感觉sschaef版本不够精确。我不太了解Scala(实际上,我从未使用过它),但我认为它不取决于函数的实现方式。 我也没有理解“类型检查器从左到右”的意思。 我没有最新版本的Scala,所以无法在你的示例venechka上进行测试,但我认为可以通过添加一些类型注释来规避类型错误/限制。例如,这里是sschaef的示例:
scala> List(1).foldLeft(Nil)((xs,x) => x::xs)
<console>:8: error: type mismatch;
 found   : List[Int]
 required: scala.collection.immutable.Nil.type
              List(1).foldLeft(Nil)((xs,x) => x::xs)
                                               ^

scala> List(1).foldLeft(Nil : List[Int])((xs,x) => x::xs)
res1: List[Int] = List(1)

我相信您可以通过执行类似以下操作来在您的示例中实现相同的效果:
Some(0).fold(new B : A){_=>new C}

我认为这可能是Scala类型检查器的限制(可能与子类型存在有关),但在肯定之前,我需要再看看。

无论如何,在这里和那里添加类型注释应该可以解决你的问题,所以请享受吧!

编辑:哦,sschaef编辑了他的答案并加入了一些解释,这可能会使我关于此行为原因的说法无效。但这并不改变类型注释将解决您的问题的事实。所以我将让这条消息保持原样。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接