让我们用抽象类
定义一个特征object Outer {
trait X {
type T
val empty: T
}
现在我们可以创建一个实例:
val x = new X {
type T = Int
val empty = 42
}
Scala现在认识到,x.empty
是Int
:
def xEmptyIsInt = x.empty: Int
现在,让我们定义另一个类
case class Y(x: X) extends X {
type T = x.T
val empty = x.empty
}
并制作一个实例
val y = Y(x)
但现在Scala,无法推断y.empty
的类型为Int
。以下
def yEmptyIsInt = y.empty: Int
现在会生成错误消息:
error: type mismatch;
found : y.x.T
required: Int
y.empty : Int
为什么会这样?这是一个scala bug吗?
我们可以通过向案例类引入参数来缓解此问题:
case class Z[U](x: X { type T = U }) extends X {
type T = U
val empty = x.empty
}
然后再次运作
val z = Z(x)
def zEmptyIsInt: Int = z.empty
但是我们总是要在呼叫现场提到X内的所有类型。理想情况下,这应该是一个实现细节,导致以下方法:
case class A[U <: X](z: U) extends X {
type T = z.T
val empty = z.empty
}
这也减轻了问题
val a = A(x)
def aEmptyIsInt: Int = a.empty
}
总而言之,我的问题如下:为什么简单的案例不起作用?这是一个有效的解决方法吗?当我们遵循两种解决方法之一时,可能会出现哪些其他问题?有更好的方法吗?
答案 0 :(得分:1)
您已经重复使用x
来处理不同的事情,所以从这里开始,我将调用由val x
“实例x”实例化的对象和Y类中使用的x: X
“参数x”。
“Instance x”是trait X
的{{3}},具体成员会覆盖trait X
的抽象成员。作为X
的子类,您可以将其传递给case class Y
的构造函数,并且它将被愉快地接受,因为作为子类, 是X
。< / p>
在我看来,您希望case class Y
然后在运行时检查 以查看它传递的X
实例是否已覆盖X
成员,并生成Y
的实例,其成员具有不同的类型,具体取决于传入的内容。
这显然不是Scala的工作方式,并且几乎会破坏其(静态)类型系统的目的。例如,如果没有运行时反射,你就无法对Y.empty做任何有用的事情,因为它可以有任何类型,那时你最好只使用动态类型系统。如果你想要参数,自由定理,不需要反射等的好处,那么你必须坚持静态确定的类型(模式匹配的一个小例外)。这就是Scala在这里所做的。
实际发生的是你告诉Y
的构造函数,参数x
是X
,因此编译器静态地确定x.empty
,具有类型X.empty
,它是抽象类型T
。斯卡拉的推断并没有失败;你的类型实际上是不匹配的。
另外,这与路径依赖类型没有任何关系。这是一个很好的anonymous subclass,但简而言之,路径依赖类型绑定到它们的父实例,而不是在运行时动态确定。