隐式的类型
Scala中的隐式指的是可以“自动”传递的值,或者从一种类型自动转换为另一种类型。
隐式转换
简单地说一下后一种类型,如果在类C的对象o上调用方法m,而这个类不支持方法m,那么Scala将寻找从C到支持m的方法的隐式转换。一个简单的例子是String上的方法映射:
"abc".map(_.toInt)
String不支持方法映射,但StringOps支持,并且有一个从String到StringOps的隐式转换可用(参见Predef上的隐式def augmentString)。
隐式参数
另一种隐式是隐式参数。这些参数像其他参数一样传递给方法调用,但编译器会尝试自动填充它们。如果做不到,它就会抱怨。您可以显式地传递这些参数,例如,这就是使用breakOut的方式(请参阅关于breakOut的问题,在您准备迎接挑战的某一天)。
在这种情况下,必须声明隐式方法的需要,例如foo方法声明:
def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}
视图边界
有一种情况,隐式既是隐式转换,也是隐式形参。例如:
def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)
getIndex("abc", 'a')
方法getIndex可以接收任何对象,只要存在从它的类到Seq[T]的隐式转换可用。正因为如此,我可以将一个String传递给getIndex,它将工作。
在幕后,编译器将seq. indexof (value)更改为conv(seq). indexof (value)。
这是非常有用的,有语法糖来写它们。使用这个语法糖,getIndex可以这样定义:
def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)
这个语法糖被描述为一个视图边界,类似于上界(CC <: Seq[Int])或下界(T >: Null)。
上下文范围
隐式参数的另一个常见模式是类型类模式。此模式允许向没有声明公共接口的类提供公共接口。它既可以用作桥接模式(获得关注点分离),也可以用作适配器模式。
您提到的Integral类是类型类模式的一个经典示例。Scala标准库的另一个例子是有序。有一个库大量使用这种模式,称为Scalaz。
下面是它使用的一个例子:
def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
它还有一种语法糖,称为上下文界限,由于需要引用隐式,它就不那么有用了。该方法的直接转换如下所示:
def sum[T : Integral](list: List[T]): T = {
val integral = implicitly[Integral[T]]
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
当您只需要将上下文边界传递给使用它们的其他方法时,上下文边界会更有用。例如,在Seq上排序的方法需要隐式排序。要创建一个方法reverseSort,可以这样写:
def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse
因为ordered [T]是隐式传递给reverseSort的,所以它可以隐式传递给sorted。
隐式是怎么来的?
当编译器看到需要隐式参数时,无论是因为你调用的方法在对象的类中不存在,还是因为你调用的方法需要隐式参数,它都会搜索一个符合需要的隐式参数。
这个搜索遵循某些规则,这些规则定义了哪些隐式是可见的,哪些是不可见的。下表显示了编译器搜索隐式函数的位置,摘自Josh Suereth关于隐式函数的精彩演示(时间戳20:20),我衷心推荐给任何想要提高Scala知识的人。从那时起,它得到了反馈和更新。
下面数字1下可用的隐式比数字2下可用的隐式优先。除此之外,如果有几个符合隐式形参类型的参数,将使用静态重载解析规则选择一个最具体的参数(参见Scala规范§6.26.3)。更详细的信息可以在答案后面的一个问题中找到。
首先看看当前范围
在当前作用域中定义的隐式
明确的进口
通配符进口
其他文件的作用域相同
现在看看相关的类型
类型的伴生对象
参数类型的隐式作用域(2.9.1)
类型参数的隐式范围(2.8.0)
嵌套类型的外部对象
其他维度
让我们给他们举几个例子:
在当前作用域中定义的隐式
implicit val n: Int = 5
def add(x: Int)(implicit y: Int) = x + y
add(5) // takes n from the current scope
明确的进口
import scala.collection.JavaConversions.mapAsScalaMap
def env = System.getenv() // Java map
val term = env("TERM") // implicit conversion from Java Map to Scala Map
通配符进口
def sum[T : Integral](list: List[T]): T = {
val integral = implicitly[Integral[T]]
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
在其他文件中相同的范围
编辑:这似乎没有不同的优先级。如果你有一些例子,说明优先级的区别,请作出评论。否则,不要依赖这个。
这类似于第一个示例,但假设隐式定义位于与其用法不同的文件中。另请参阅包对象如何用于引入隐式。
类型的伴生对象
这里有两个值得注意的对象同伴。首先,研究“源”类型的对象伙伴。例如,在对象Option中有一个到Iterable的隐式转换,因此可以在Option上调用Iterable方法,或者将Option传递给需要Iterable的对象。例如:
for {
x <- List(1, 2, 3)
y <- Some('x')
} yield (x, y)
该表达式由编译器翻译为
List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))
然而,清单。flatMap需要一个TraversableOnce,而Option不是。编译器然后查看Option的对象伙伴,并找到到Iterable的转换,这是一个TraversableOnce,使这个表达式正确。
第二,期望类型的伴生对象:
List(1, 2, 3).sorted
sorted方法采用隐式ordered。在本例中,它在order类的同伴order对象中查找,并在那里找到一个隐式的order [Int]。
注意,还将研究超类的伴生对象。例如:
class A(val n: Int)
object A {
implicit def str(a: A) = "A: %d" format a.n
}
class B(val x: Int, y: Int) extends A(y)
val b = new B(5, 2)
val s: String = b // s == "A: 2"
这就是Scala如何在你的问题中找到隐式Numeric[Int]和Numeric[Long],顺便说一下,因为它们是在Numeric中找到的,而不是Integral。
参数类型的隐式作用域
如果你有一个参数类型为a的方法,那么类型a的隐式作用域也会被考虑。通过“隐式作用域”,我的意思是所有这些规则都将递归地应用——例如,根据上面的规则,将搜索A的伴生对象以寻找隐式。
请注意,这并不意味着将搜索A的隐式作用域以查找该参数的转换,而是整个表达式。例如:
class A(val n: Int) {
def +(other: A) = new A(n + other.n)
}
object A {
implicit def fromInt(n: Int) = new A(n)
}
// This becomes possible:
1 + new A(1)
// because it is converted into this:
A.fromInt(1) + new A(1)
这是从Scala 2.9.1开始提供的。
类型参数的隐式作用域
这是使类型类模式真正工作所必需的。例如,考虑ordered:它在其伴生对象中带有一些隐式,但您不能向它添加东西。那么,如何为自动找到的自己的类创建一个order呢?
让我们从实现开始:
class A(val n: Int)
object A {
implicit val ord = new Ordering[A] {
def compare(x: A, y: A) = implicitly[Ordering[Int]].compare(x.n, y.n)
}
}
所以,考虑一下你打电话的时候会发生什么
List(new A(5), new A(2)).sorted
正如我们所看到的,sorted方法期望一个ordered [A](实际上,它期望一个ordered [B],其中B >: A)。在ordered内部没有任何这样的东西,也没有“source”类型可供查看。显然,它是在A中找到它,A是order的类型参数。
这也是期望CanBuildFrom的各种收集方法的工作方式:隐式在CanBuildFrom的类型参数的伴生对象中找到。
注:ordered定义为trait ordered [T],其中T为类型参数。在前面,我说过Scala在类型参数内部进行检查,这没什么意义。上面隐式查找的是orders [A],其中A是实际类型,而不是类型参数:它是orders的类型参数。请参见Scala规范的7.2节。
这是从Scala 2.8.0开始提供的。
嵌套类型的外层对象
我还没见过这样的例子。如果有人能和我分享,我会很感激的。原理很简单:
class A(val n: Int) {
class B(val m: Int) { require(m < n) }
}
object A {
implicit def bToString(b: A#B) = "B: %d" format b.m
}
val a = new A(5)
val b = new a.B(3)
val s: String = b // s == "B: 3"
其他维度
我很确定这是一个笑话,但这个答案可能不是最新的。因此,不要把这个问题作为正在发生的事情的最终仲裁者,如果你注意到它已经过时了,请通知我,以便我可以修复它。
EDIT
相关问题:
上下文和视图边界
值得一提的是链接
Scala:隐式参数解析优先级