具有泛型和协方差的Scala类型推断,scalac中可能存在的问题

时间:2011-12-20 22:04:56

标签: generics scala inheritance

我有一个我今天发现的问题的简短列表。 我是scala新手,因此我的问题可能很简单。

我们假设我们有一个像这样的类:

abstract class A[+T] { def foo[S >: T](x: S): String }

我们应该为有用的子类提供一些功能。

1)我的第一次尝试看起来像这样:

class B extends A[String] { def foo(x: String) = x }

但是scalac编译器不同意消息:

xxx@yyy:~$ scalac covariant.scala 
covariant.scala:3: error: class B needs to be abstract, since method foo in class A of type [S >: String](x: S)String is not defined
class B extends A[String] { def foo(x: String) = x }
      ^

首先,为什么scalac没有推断foo的泛型类型参数,这真的是一项复杂的任务吗?

2)接下来尝试看起来更好,应该被接受,我认为:

class B extends A[String] { def foo[String](x: String) = x }

但是现在编译器让我大开眼界:

covariant.scala:3: error: type mismatch;
 found   : String(in method foo)
 required: java.lang.String
class B extends A[String] { def foo[String](x: String) = x }
                                                         ^

看起来像String和java.lang.String不匹配的问题。这是第二个问题:这真的是一个错误吗?

3)最后我决定将String类型切换为Date作为参数:

import java.util.Date
class B extends A[Date] { def foo[Date](x: Date) = x.toString }

它编译时没有任何警告。最后一个问题是:为什么?我的第二个和第三个片段之间有什么区别?

顺便说一下,scalac版本是2.9.1.final

1 个答案:

答案 0 :(得分:7)

真的,你所有的问题都源于同样的误解。

你定义为“一个带有类型参数的类和一个方法foo,它可以作用于T 的任何类型 下降,T从中下载。

你提供了一个带有foo的类,它可以作用于T而不是必然 后代(实际上,因为参数是协变的,foo实际上可以用于后代但是规范不是足以让人抓住那个超级班的T.

在其他两个问题中,您不小心重用了现有类型的名称。当你编写foo [Date]或foo [String]时,你并没有像你显然那样提到java.lang.String或java.lang.Date,而是指同名的新类型!这就是为什么你看到String和java.lang.String不匹配的原因 - 它们是两种不同的类型。

请改为尝试:

abstract class A[+T] { def foo[S >: T](x: S): String }
class B extends A[String] { def foo[S >: String](x: S) = x.toString }

是的,S >: String是没有意义的,因为String是最终的,但规范无法划分出每一个可能的边缘情况。

注意我也是一个tyro,所以如果我的回答错误/误导,请不要感到震惊。请记住:这个建议保证价值是您支付的两倍,或者您的钱被高兴地退还。

TOLD YA 大错误:我推翻了超类和子类。内联修正与三振出

示例 OP不清楚为什么所有这些都是必要的。考虑以下功能

def foo3(a : A[String]) = a.foo(3)

这是合法的,因为3(作为java.lang.Integer)是一个Object的例子,它是A的祖先。如果B.foo被定义为接受一个字符串而没有别的,B的实例不能是传入foo3。