X =:= Y
只是类型的语法糖(中缀表示法)=:=[X, Y]
.
所以当你这样做时implicitly[Y =:= Y]
,您只需查找类型的隐式值=:=[X, Y]
.
=:=
是定义在中的通用特征Predef
.
Also, =:=
是一个有效的类型名称,因为类型名称(就像任何标识符一样)可以包含特殊字符。
从现在起,我们重新命名吧=:=
as IsSameType
并删除中缀符号,以使我们的代码看起来更简单,更不那么神奇。
这给了我们implicitly[IsSameType[X,Y]]
以下是该类型定义方式的简化版本:
sealed abstract class IsSameType[X, Y]
object IsSameType {
implicit def tpEquals[A] = new IsSameType[A, A]{}
}
注意如何tpEquals
提供隐含值IsSameType[A, A]
对于任何类型A
。
换句话说,它提供了一个隐含的值IsSameType[X, Y]
当且仅当X
and Y
是同一类型。
所以implicitly[IsSameType[Foo, Foo]]
编译得很好。
但implicitly[IsSameType[Int, String]]
不会,因为类型范围没有隐式IsSameType[Int, String]
, 鉴于tpEquals
在这里不适用。
因此,通过这个非常简单的构造,我们可以静态地检查某些类型X
与其他类型相同Y
.
现在这里是一个示例,说明它的用途。假设我想定义一个 Pair 类型(忽略它已经存在于标准库中的事实):
case class Pair[X,Y]( x: X, y: Y ) {
def swap: Pair[Y,X] = Pair( y, x )
}
Pair
是用它的 2 个元素的类型参数化的,这两个元素可以是任何东西,最重要的是不相关。
现在如果我想定义一个方法怎么办toList
将这对转换为 2 元素列表?
这种方法只有在以下情况下才真正有意义X
and Y
是一样的,否则我将被迫返回List[Any]
。
我当然不想改变的定义Pair
to Pair[T]( x: T, y: T )
因为我真的希望能够拥有成对的异构类型。
毕竟只是在打电话的时候toList
我需要 X == Y。所有其他方法(例如swap
) 应该可以在任何类型的异质对上调用。
所以最后我真的想静态地确保 X == Y,但只有在调用时toList
,在这种情况下,可以并且一致地返回List[X]
(or a List[Y]
,这是同一件事):
case class Pair[X,Y]( x: X, y: Y ) {
def swap: Pair[Y,X] = Pair( y, x )
def toList( implicit evidence: IsSameType[X, Y] ): List[Y] = ???
}
但实际执行起来还存在严重问题toList
。如果我尝试编写明显的实现,则无法编译:
def toList( implicit evidence: IsSameType[X, Y] ): List[Y] = List[Y]( x, y )
编译器会抱怨x
不属于类型Y
。确实,X
and Y
就编译器而言,它们仍然是不同的类型。
只有通过仔细构造,我们才能静态地确定 X == Y
(即,事实是toList
接受类型的隐式值IsSameType[X, Y]
,并且它们是由方法提供的tpEquals
仅当 X == Y 时)。
但编译器肯定不会破译这个精明的结构来得出 X == Y 的结论。
为了解决这种情况,我们可以做的是提供从 X 到 Y 的隐式转换,前提是我们知道 X == Y(或者换句话说,我们有一个实例)IsSameType[X, Y]
在适用范围)。
// A simple cast will do, given that we statically know that X == Y
implicit def sameTypeConvert[X,Y]( x: X )( implicit evidence: IsSameType[X, Y] ): Y = x.asInstanceOf[Y]
现在,我们的实施toList
最终编译良好:x
将简单地转换为Y
通过隐式转换sameTypeConvert
.
作为最后的调整,我们可以进一步简化事情:假设我们采用隐式值(evidence
)已经作为参数,
为什么不让这个值实现转换?像这样:
sealed abstract class IsSameType[X, Y] extends (X => Y) {
def apply( x: X ): Y = x.asInstanceOf[Y]
}
object IsSameType {
implicit def tpEquals[A] = new IsSameType[A, A]{}
}
然后我们可以删除该方法sameTypeConvert
,因为隐式转换现在由IsSameType
实例本身。
现在IsSameType
有双重目的:静态地确保 X == Y,并且(如果是的话)提供隐式转换,实际上允许我们处理X
作为实例Y
.
我们现在基本上重新实现了类型=:=
定义如下Predef
UPDATE:从评论中可以看出,使用asInstanceOf
困扰着人们(尽管它实际上只是一个实现细节,并且没有用户IsSameType
需要进行演员表)。事实证明,即使在实施过程中也很容易摆脱它。看哪:
sealed abstract class IsSameType[X, Y] extends (X => Y) {
def apply(x: X): Y
}
object IsSameType {
implicit def tpEquals[A] = new IsSameType[A, A]{
def apply(x: A): A = x
}
}
基本上,我们只需离开apply
抽象,并且只在中正确实现它tpEquals
我们(和编译器)知道传递的参数和返回值确实具有相同的类型。因此不需要任何演员阵容。确实如此。
请注意,最后,相同的转换仍然存在于生成的字节码中,但现在源代码中不存在,并且从编译器的角度来看可以证明是正确的。尽管我们did引入一个附加的(匿名)类(因此从抽象类到具体类的附加间接),它should在任何像样的虚拟机上运行得一样快,因为我们处于“单态方法调度”的简单情况(如果您对虚拟机的内部工作感兴趣,请查找它)。尽管它可能仍然使虚拟机更难内联调用apply
(运行时虚拟机优化是一种黑术,很难做出明确的说法)。
最后一点,我必须强调,如果代码被证明是正确的,那么无论如何在代码中进行强制转换确实没什么大不了的。毕竟,标准库本身直到最近都有同样的强制转换(现在整个实现已经过修改,使其看起来更强大,但在其他地方仍然包含强制转换)。如果它对于标准库来说足够好,那么对我来说也足够好。