Scala在哪里寻找隐含的东西?

Scala的新手隐含的问题似乎是:编译器在哪里查找implicits? 我的意思是隐含的,因为这个问题似乎从来没有完全形成,好像没有文字。 :-)例如,下面的integral值来自哪里?

 scala> import scala.math._ import scala.math._ scala> def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)} foo: [T](t: T)(implicit integral: scala.math.Integral[T])Unit scala> foo(0) scala.math.Numeric$IntIsIntegral$@3dbea611 scala> foo(0L) scala.math.Numeric$LongIsIntegral$@48c610af 

另一个决定学习第一个问题的答案的问题是编译器如何select使用哪种隐含的,在某些明显不明确的情况下(但无论如何编译)?

例如, scala.Predef定义了两个String转换:一个转换为WrappedString ,另一个转换为StringOps 。 然而,这两个类都有很多方法,所以为什么Scala在调用map时不会抱怨模糊的问题呢?

注:这个问题受到另一个问题的启发,希望以更一般的方式说明问题。 这个例子是从那里复制的,因为它在答案中被引用。

隐含types

在Scala中的含义是指一个可以自动传递的值,也就是说,可以自动传递一个types到另一个types的值。

隐式转换

简单地说一下后一种types,如果在类C一个对象o上调用一个方法m ,并且该类不支持方法m ,那么Scala将查找从C到隐式的转换,支持m 。 一个简单的例子就是String上的方法map

 "abc".map(_.toInt) 

String不支持方法map ,但是StringOps支持,并且存在从StringStringOps的隐式转换(请参阅implicit def augmentString上的implicit def augmentString Predef )。

隐式参数

另一种隐含的是隐式参数 。 这些被传递给像任何其他参数的方法调用,但编译器会尝试自动填充它们。 如果不行,就会抱怨。 例如, 可以显式地传递这些参数,例如,如何使用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 )。

上下文界限

隐式参数中的另一个常见模式是types模式 。 这种模式可以为没有声明它们的类提供通用的接口。 它既可以作为桥梁模式 – 获得问题的分离 – 也可以作为适配器模式。

您提到的Integral类是类类模式的经典示例。 Scala标准库上的另一个例子是Ordering 。 有一个图书馆,大量使用这种模式,称为斯卡拉斯。

这是一个使用的例子:

 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)(_ + _) } 

当你只需要将它们传递给使用它们的其他方法时,上下文边界更有用。 例如, sorted Seq sorted的方法需要一个隐式的Ordering 。 要创build一个方法reverseSort ,可以写:

 def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse 

因为Ordering[T]被隐式地传递给了reverseSort ,所以它可以将它隐式地传递给sorted

Implicits从哪里来?

当编译器认为需要隐式的时候,要么是因为你调用的对象的类不存在的方法,要么是因为你正在调用一个需要一个隐式参数的方法,它会search一个隐含的, 。

这种search服从某些规则,这些规则定义哪些隐含是可见的,哪些不是。 下面的表格显示了编译器在何处searchimplicits是从一个关于Josh Suereth的暗示的精彩演示中获得的,我衷心推荐给那些希望改进Scala知识的人。 自那时以来,它一直得到反馈和更新的补充。

在下面的编号1下可用的含义优先于在编号2下的含义。除此之外,如果有几个符合隐式参数types的符合条件的参数,将使用静态重载parsing规则select最具体的参数(参见Scala规范§6.26.3)。 更详细的信息可以在我在这个答案的结尾处链接到的问题中find。

  1. 首先看当前的范围
    • 在当前范围内定义的含义
    • 显式导入
    • 通配符导入
    • 其他文件中的范围相同
  2. 现在看看关联的types
    • types的伴随对象
    • 参数types的隐含范围(2.9.1)
    • types参数的隐式范围(2.8.0)
    • 嵌套types的外部对象
    • 其他维度

我们来举个例子吧:

在当前范围内定义的含义

 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)(_ + _) } 

同样的范围在其他文件

编辑 :这似乎没有不同的优先顺序。 如果你有一些例子说明了优先区分,请发表评论。 否则,不要依赖这个。

这就像第一个例子,但假设隐式定义与其用法不同的文件。 另请参阅包对象可能如何用于引入含义。

types的伴侣对象

这里有两个对象的注意事项。 首先,研究“源”types的对象伴侣。 例如,在对象Option有一个隐式转换为Iterable ,因此可以在Option上调用Iterable方法,或者将Option传递给期望Iterable东西。 例如:

 for { x <- List(1, 2, 3) y <- Some('x') } yield, (x, y) 

该expression式被编译器翻译为

 List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y))) 

但是, List.flatMap需要一个TraversableOnce ,哪个Option不是。 然后,编译器在Option的对象伴侣内查找并find转换为Iterable (这是TraversableOnce ,从而使该expression式正确。

其次,预期types的​​伴侣对象:

 List(1, 2, 3).sorted 

sorted的方法采用隐式Ordering 。 在这种情况下,它在对象Ordering查找,伴随着Ordering类,并在那里find一个隐式的Ordering[Int]

请注意,超级类的伴侣对象也被调查。 例如:

 class A(val n: Int) object A { implicit def str(a: A) = "A: %d" format an } class B(val x: Int, y: Int) extends A(y) val b = new B(5, 2) val s: String = b // s == "A: 2" 

这就是Scala如何在你的问题中find暗含的Numeric[Int]Numeric[Long] ,顺便说一句,因为它们是在Numeric中find的,而不是Integral

一个参数types的隐式范围

如果你有一个参数types为A的方法,那么typesA的隐式范围也将被考虑。 所谓“隐式范围”,是指所有这些规则将被recursion地应用 – 例如,根据上面的规则, A的伴随对象将被search隐含。

请注意,这并不意味着A的隐式范围将被search该参数的转换,而是整个expression式的转换。 例如:

 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) 

这是从斯卡拉2.9.1以来。

types参数的隐式范围

这是使types模式真正起作用所必需的。 考虑Ordering ,例如:它的伴侣对象带有一些暗示,但是你不能添加东西。 那么怎样才能自动find自己的class级呢?

让我们从实施开始:

 class A(val n: Int) object A { implicit val ord = new Ordering[A] { def compare(x: A, y: A) = implicitly[Ordering[Int]].compare(xn, yn) } } 

所以,考虑一下你打电话时会发生什么

 List(new A(5), new A(2)).sorted 

正如我们所看到的, sorted的方法需要一个Ordering[A] (实际上,它期望一个Ordering[B] ,其中B >: A )。 Ordering没有这样的东西,并且没有“源”types。 显然,它是在AfindA ,这是Ordering一个types参数

这也是期望CanBuildFrom各种各样的收集方法的工作原理:在伴随对象内部findCanBuildFromCanBuildFromtypes参数中的CanBuildFrom

Ordering被定义为trait Ordering[T] ,其中T是一个types参数。 以前我说过Scala看内部types参数,这没什么意义。 上面的隐式查找是Ordering[A] ,其中A是实际types,而不是types参数:它是Orderingtypes参数 。 参见Scala规范的7.2节。

这是自Scala 2.8.0以来。

嵌套types的外部对象

我还没有看到这个例子。 如果有人能分享一个,我将不胜感激。 原则很简单:

 class A(val n: Int) { class B(val m: Int) { require(m < n) } } object A { implicit def bToString(b: A#B) = "B: %d" format bm } val a = new A(5) val b = new aB(3) val s: String = b // s == "B: 3" 

其他维度

我很确定这是一个笑话,但这个答案可能不是最新的。 所以,不要把这个问题当作最后的仲裁者,如果你注意到它已经过时了,请告诉我,以便我可以修复它。

编辑

感兴趣的相关问题:

  • 上下文和视图边界
  • 链接牵连
  • Scala:隐式参数parsing优先

我想知道隐式参数parsing的优先级,而不仅仅是它在哪里寻找,所以我写了一篇博客文章, 重新回顾implicits没有import税 (一些反馈后再次隐式参数优先 )。

列表如下:

  • 1)通过本地声明,导入,外部作用域,inheritance,可以不带前缀访问的包对象,对当前调用范围隐含可见。
  • 2) 隐式作用域 ,它包含所有types的伴随对象和包对象,这些对象和包对象与我们search的隐式types有一定的关系(即types的包对象,types本身的伴随对象,types构造器(如果有的话),其参数(如果有的话),还有它的超types和超types)。

如果在任一阶段我们发现多个隐式静态重载规则被用来解决它。