我一直在阅读有关Scala类型级编程的一些内容。主要是Apocalisp博客,也是Alexander Lehmann的youtube演讲。
我有点卡在我认为可能非常基本的东西上,这是使用隐式比较两种类型,如下所示:
implicitly[Int =:= Int]
Apocalisp博客上的Mark说:
这对于捕获范围内的隐式值并且类型为T。
非常有用
我知道如何使这项工作,但我不知道为什么它的工作,所以不想继续前进。
在上面的情况下,在作用域中有一个隐式的“Int”类型,“隐式地”从以太中获取,允许代码编译?这如何适合'function1'返回类型?
res0: =:=[Int,Int] = <function1>
此外,这隐含的来自哪里?在我的特质'Foo'的情况下,为什么呢?
implicitly[Foo =:= Foo]
编译?在这种情况下,“Foo”隐含在哪里?
如果这是一个非常愚蠢的问题,请提前道歉,并感谢您的帮助!
答案 0 :(得分:17)
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
)作为参数,
为什么没有这个值实现转换?像这样:
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
类型