隐式使用Scala实现类型相等
我一直在读一些关于Scala类型级编程的东西。主要是Apocalisp博客,还有Alexander Lehmann在youtube上的演讲 我有点被困在一些我想可能是非常基本的东西上,即使用隐式比较两种类型,如下所示:隐式使用Scala实现类型相等,scala,types,implicit,Scala,Types,Implicit,我一直在读一些关于Scala类型级编程的东西。主要是Apocalisp博客,还有Alexander Lehmann在youtube上的演讲 我有点被困在一些我想可能是非常基本的东西上,即使用隐式比较两种类型,如下所示: implicitly[Int =:= Int] Apocalisp博客上的马克说: 这对于捕获范围内且类型为T的隐式值非常有用 我知道如何使这项工作,但我真的不知道为什么它的工作,所以不想继续 在上面的例子中,作用域中是否有一个类型为“Int”的隐式函数,它“隐式地”从以太中提
implicitly[Int =:= Int]
Apocalisp博客上的马克说:
这对于捕获范围内且类型为T的隐式值非常有用
我知道如何使这项工作,但我真的不知道为什么它的工作,所以不想继续
在上面的例子中,作用域中是否有一个类型为“Int”的隐式函数,它“隐式地”从以太中提取,允许代码编译?这与的“function1”返回类型有什么关系
res0: =:=[Int,Int] = <function1>
编译?在这种情况下,“Foo”隐式从何而来
如果这是一个非常愚蠢的问题,请提前道歉,并感谢您的帮助
X=:=Y
只是类型=:=[X,Y]
的语法糖(中缀符号)
因此,当您隐式地执行[Y=:=Y]
时,您只需查找类型为=:=[X,Y]
的隐式值。
=:=
是在Predef
中定义的一般特征
另外,=:=
是有效的类型名,因为类型名(就像任何标识符一样)可以包含特殊字符
从现在起,让我们将=:=
重命名为IsSameType
,并删除中缀符号,以使代码看起来更简单、更不神奇。
这就隐式地为我们提供了[IsSameType[X,Y]]
以下是如何定义此类型的简化版本:
sealed abstract class IsSameType[X, Y]
object IsSameType {
implicit def tpEquals[A] = new IsSameType[A, A]{}
}
注意tpEquals
如何为任何类型A
提供IsSameType[A,A]
的隐式值。
换句话说,当且仅当X
和Y
是同一类型时,它提供IsSameType[X,Y]
的隐式值。
因此隐式[IsSameType[Foo,Foo]]
编译得很好。
但是隐式[IsSameType[Int,String]]
没有,因为类型IsSameType[Int,String]
的作用域中没有隐式,因为这里不适用tpEquals
因此,通过这个非常简单的构造,我们能够静态地检查某个类型X
是否与另一个类型Y
相同
现在,这里有一个例子说明它是如何有用的。假设我想定义一个对类型(忽略它已经存在于标准库中的事实):
Pair
通过其2个元素的类型进行参数化,这2个元素可以是任何元素,最重要的是它们之间不相关。
现在,如果我想定义一个方法toList
,将对转换为2元素列表,该怎么办?
这种方法只有在X
和Y
相同的情况下才有意义,否则我将被迫返回列表[Any]
。
我当然不想把Pair
的定义改为Pair[t](x:t,y:t)
,因为我真的想拥有异构类型的对。
毕竟,只有在调用toList
时,我才需要X==Y。所有其他方法(如swap
)都应该可以在任何类型的异构对上调用。
因此,最后我真的希望静态地确保X==Y,但仅当调用toList
时,在这种情况下,返回一个List[X]
(或一个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]
)
现在,我们对toList
的实现最终编译良好:x
将通过隐式转换sameTypeConvert
简单地转换为Y
作为最后一个调整,我们可以进一步简化:假设我们已经将隐式值(证据
)作为参数,
为什么不让这个值实现转换呢?像这样:
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
更新:从评论中可以看出,使用
替代
似乎让人们感到困扰(尽管它实际上只是一个实现细节,而且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
中正确实现它,在这里我们(和编译器)知道传递的参数和返回值实际上具有相同的类型。因此,不需要任何铸造。真的是这样
请注意,最后,相同的演员阵容
def toList( implicit evidence: IsSameType[X, Y] ): List[Y] = List[Y]( 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]
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]{}
}
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
}
}