隐式使用Scala进行类型等式

时间:2014-03-28 13:45:21

标签: 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”隐含在哪里?

如果这是一个非常愚蠢的问题,请提前道歉,并感谢您的帮助!

1 个答案:

答案 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,该怎么办? 在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)作为参数, 为什么没有这个值实现转换?像这样:

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类型