scala - 隐式使用Scala进行类型相等

标签 scala types implicit

我一直在阅读有关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,该怎么办?
该方法仅在XY相同的情况下才有意义,否则我将被迫返回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类型。实际上,就编译器而言,XY仍然是不同的类型。
只有精心构建,我们才能静态确定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/

相关文章:

scala - 从 2.0 开始如何启动 Akka Actors?

scala - 无法覆盖具有非 volatile 上限的类型

assembly - 什么是数据类型以及它是如何实现的?

Scala - 将列表列表转换为单个列表 : List[List[A]] to List[A]

scala - 错误:对象rocksdb不是软件包orgs的成员

ios - 如何定义一个可能是两个对象之一的 var?

scala - 在 scala 中的类中使用数字类型界限的正确方法

德尔福(-XE): casting to a record type with implicit conversion

scala - 为什么Scala 不在这里使用隐式转换?

java - Scala/Java语法,返回接口(interface)实现