我一直在阅读有关Scala类型级别编程的内容。主要是Apocalisp博客,以及Alexander Lehmann的youtube演讲。
我有点卡住了一些我认为可能很基本的东西,即隐式比较两种类型,如下所示:
implicitly[Int =:= Int]
Apocalisp博客上的Mark说:
这对于捕获范围内且类型为T的隐式值很有用。
我知道如何进行这项工作,但是我真的不知道为什么会这样,所以不想继续。
在上述情况下,范围内是否存在隐式类型为“ Int”的隐式类型,该隐式类型是从以太中隐式提取的,从而允许代码进行编译?这与'function1'返回类型如何匹配?
res0: =:=[Int,Int] = <function1>
另外,这种隐式从何而来?以我的特质“ Foo”为例,为什么
implicitly[Foo =:= Foo]
编译?在这种情况下,“ Foo”隐式从哪里来?
如果这是一个非常愚蠢的问题,请提前致歉,并感谢您的帮助!
最佳答案
X =:= Y
只是类型=:=[X, Y]
的语法糖(中缀符号)。
因此,当您执行implicitly[Y =:= Y]
时,您只是在查找类型为=:=[X, Y]
的隐式值。=:=
是Predef
中定义的通用特征。
另外,=:=
是有效的类型名称,因为类型名称(就像任何标识符一样)可以包含特殊字符。
从现在开始,让我们将=:=
重命名为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
是相同类型时,它提供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个元素的类型进行参数化,可以是任何元素,最重要的是不相关。现在,如果我想定义将对转换为2个元素列表的方法
toList
,该怎么办?该方法仅在
X
和Y
相同的情况下才有意义,否则我将被迫返回List[Any]
。而且我当然不想将
Pair
的定义更改为Pair[T]( x: T, y: T )
,因为我真的希望能够具有成对的异构类型。毕竟,只有在调用
toList
时,我才需要X ==Y。所有其他方法(例如swap
)应可在任何种类的异类对上调用。因此,最后,我真的想静态地确保X == Y,但仅当调用
toList
时,在这种情况下才有可能且一致地返回List[X]
(或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
和Y
仍然是不同的类型。只有精心构建,我们才能静态确定X == Y
(即,
toList
采用类型为IsSameType[X, Y]
的隐式值,并且仅当X == Y时,才由方法tpEquals
提供它们的事实)。但是编译器当然不会破译这种精巧的结构来得出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
)作为参数,为什么没有THIS值实现转换?像这样:
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
类型。更新:从评论中可以明显看出,使用
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
中正确实现它,我们(和编译器)知道传递的参数和返回值实际上具有相同的类型。因此,不需要任何演员表。就是这样。请注意,最后,所生成的字节码中仍存在相同的强制类型转换,但是源代码中现在不存在这种类型转换,并且从编译器的角度来看,它可以证明是正确的。尽管我们确实引入了一个额外的(匿名)类(因此引入了从抽象类到具体类的额外间接),但是它应该在任何像样的虚拟机上都一样快地运行,因为我们处于“单态方法”的简单情况下派遣”(如果您对虚拟机的内部运作感兴趣,请查阅)。尽管这可能仍然会使虚拟机更难内联
apply
的调用(运行时虚拟机优化是一种黑手艺,并且很难做出明确的声明)。最后一点,我要强调的是,如果可以证明是正确的话,对代码进行强制转换实在没什么大不了的。毕竟,直到最近,标准库本身还是具有相同的转换(现在已对整个实现进行了改进,使其功能更加强大,但仍在其他地方包含转换)。如果对标准库足够好,那么对我也足够。
关于scala - 隐式使用Scala进行类型相等,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22714609/