Scala在哪里寻找暗示?

Dan*_*ral 389 scala implicit-conversion implicits

对Scala新手的一个隐含问题似乎是:编译器在哪里寻找隐含?我的意思是隐含的,因为这个问题似乎永远不会完全形成,好像没有它的话.:-)例如,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
Run Code Online (Sandbox Code Playgroud)

对于那些决定学习第一个问题的答案的人来说,另一个问题是,在某些明显模糊的情况下(但无论如何编译),编译器如何选择使用哪个隐式?

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

注意:这个问题的灵感来自另一个问题,希望以更一般的方式陈述问题.该示例是从那里复制的,因为它在答案中被引用.

Dan*_*ral 547

含义的类型

Scala中的Implicits指的是可以"自动"传递的值,可以这么说,或者是自动转换为从一种类型到另一种类型的转换.

隐式转换

说到非常简单说一下后一种类型,如果调用一个方法m的对象上o的一类C,而这个类不支持的方法m,然后将斯卡拉寻找从隐式转换C的东西,支持m.一个简单的例子是方法mapString:

"abc".map(_.toInt)
Run Code Online (Sandbox Code Playgroud)

String不支持该方法map,但StringOps确实如此,并且隐式转换StringStringOps可用(请参阅implicit def augmentString参考资料Predef).

隐含参数

另一种隐含的是隐式参数.它们像任何其他参数一样传递给方法调用,但编译器会尝试自动填充它们.如果不能,它会抱怨.人们可以明确地传递这些参数,breakOut例如(例如breakOut,在你感觉挑战的那一天,请参阅问题).

在这种情况下,必须声明需要隐式,例如foo方法声明:

def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}
Run Code Online (Sandbox Code Playgroud)

查看边界

有一种情况,隐式是隐式转换和隐式参数.例如:

def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)

getIndex("abc", 'a')
Run Code Online (Sandbox Code Playgroud)

getIndex只要存在从其类可用的隐式转换,该方法就可以接收任何对象Seq[T].因此,我可以传递StringgetIndex它,它会起作用.

在幕后,编译器更改seq.IndexOf(value)conv(seq).indexOf(value).

这非常有用,有写句法糖.使用这种语法糖,getIndex可以像这样定义:

def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)
Run Code Online (Sandbox Code Playgroud)

这种语法糖被描述为视图边界,类似于上限(CC <: Seq[Int])或下限(T >: Null).

上下文边界

隐式参数中的另一种常见模式是类型类模式.此模式允许为未声明它们的类提供公共接口.它既可以作为桥梁模式 - 获得关注点的分离 - 也可以作为适配器模式.

Integral您提到的类是类型类模式的典型示例.Scala标准库的另一个例子是Ordering.有一个库大量使用这种模式,称为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)(_ + _)
}
Run Code Online (Sandbox Code Playgroud)

它也有语法糖,称为上下文绑定,由于需要引用隐式,因此它不太有用.该方法的直接转换如下所示:

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)(_ + _)
}
Run Code Online (Sandbox Code Playgroud)

当您只需它们传递给使用它们的其他方法时,上下文边界会更有用.例如,该方法sortedSeq需要的隐式Ordering.要创建一个方法reverseSort,可以写:

def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse
Run Code Online (Sandbox Code Playgroud)

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

Implicits来自哪里?

当编译器看到需要隐式时,或者因为你正在调用一个在对象类上不存在的方法,或者因为你正在调用一个需要隐式参数的方法,它会搜索一个适合需要的隐式.

此搜索遵循某些规则,这些规则定义哪些隐含可见,哪些不可见.下表显示了编译器搜索implicits的位置,取自Josh Suereth关于implicits 的精彩演示,我衷心向所有想要提高Scala知识的人推荐.从那时起,它就得到了补充和反馈.

在1号可用的implicits下面的优先级高于下2号除此之外的那些,如果有匹配的隐含参数的类型,最具体的人会使用静态重载决议规则来选择其中几个符合条件的参数(见斯卡拉规范§6.26.3).更详细的信息可以在我在本答案末尾链接的问题中找到.

  1. 首先看当前范围
    • 当前范围中定义的隐含
    • 明确的进口
    • 通配符导入
    • 其他文件中的范围相同
  2. 现在看看相关的类型
    • 一种类型的伴随对象
    • 参数类型的隐含范围(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
Run Code Online (Sandbox Code Playgroud)

明确的进口

import scala.collection.JavaConversions.mapAsScalaMap
def env = System.getenv() // Java map
val term = env("TERM")    // implicit conversion from Java Map to Scala Map
Run Code Online (Sandbox Code Playgroud)

通配符进口

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)(_ + _)
}
Run Code Online (Sandbox Code Playgroud)

其他文件中的相同范围

编辑:似乎这没有不同的优先权.如果您有一些示例表明优先级区别,请发表评论.否则,不要依赖这个.

这与第一个示例类似,但假设隐式定义与其使用位于不同的文件中.另请参阅如何使用包对象来引入含义.

一种类型的伴随对象

这里有两个对象伴侣.首先,查看"源"类型的对象伴随.例如,在对象内部Option有一个隐式转换Iterable,因此可以调用Iterable方法Option,或传递Option给期望的东西Iterable.例如:

for {
    x <- List(1, 2, 3)
    y <- Some('x')
} yield (x, y)
Run Code Online (Sandbox Code Playgroud)

该表达式由编译器翻译为

List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))
Run Code Online (Sandbox Code Playgroud)

但是,List.flatMap期待一个TraversableOnce,这Option不是.编译器然后查看Option对象伴随,并找到转换为Iterable,这是一个TraversableOnce,使这个表达式正确.

第二,预期类型的​​伴随对象:

List(1, 2, 3).sorted
Run Code Online (Sandbox Code Playgroud)

该方法sorted采用隐含的方式Ordering.在这种情况下,它查看对象内部Ordering,类的伴随Ordering,并在Ordering[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"
Run Code Online (Sandbox Code Playgroud)

这是斯卡拉如何发现隐含的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)
Run Code Online (Sandbox Code Playgroud)

这是自Scala 2.9.1以来可用的.

隐式范围的类型参数

这是使类型类模式真正起作用所必需的.例如Ordering,考虑一下:它的伴随对象中有一些含义,但是你不能添加东西.那么如何Ordering为自己的班级自动找到?

让我们从实现开始:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

List(new A(5), new A(2)).sorted
Run Code Online (Sandbox Code Playgroud)

正如我们所看到的,该方法sorted需要一个Ordering[A](实际上,它期望一个Ordering[B],在哪里B >: A).里面没有任何这样的东西Ordering,并且没有"源"类型可供查看.显然,这是里面找到它A,这是一个类型参数Ordering.

这也是各种收集方法期望CanBuildFrom工作的方式:在伴随对象中找到的implicits类型参数CanBuildFrom.

注意:Ordering定义为trait Ordering[T],其中T是类型参数.以前,我说Scala查看了内部类型参数,这没有多大意义.隐含找上方Ordering[A],其中A是一个实际的类型,而不是输入参数:它是一个类型参数Ordering.请参阅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"
Run Code Online (Sandbox Code Playgroud)

其他尺寸

我很确定这是一个玩笑,但这个答案可能不是最新的.因此,不要将此问题视为正在发生的事情的最终仲裁者,如果您确实注意到它已经过时,请通知我,以便我可以解决它.

编辑

相关问题:

  • 现在是时候开始在书中使用你的答案了,现在只需将它们放在一起就可以了. (59认同)
  • @pedrofurla我被认为是用葡萄牙语写一本书.如果有人能找到我与技术出版商的联系... (3认同)
  • 还搜索该类型的部分的伴随的包对象.http://lampsvn.epfl.ch/trac/scala/ticket/4427 (2认同)
  • 是的,所以http://stackoverflow.com/questions/8623055具体涵盖了这一点,但我注意到你写了"以下列表打算以优先顺序呈现......请报告." 基本上,内部列表应该是无序的,因为它们都具有相同的权重(至少在2.10中). (2认同)

Eug*_*ota 23

我想找出隐式参数解析的优先级,而不仅仅是它寻找的位置,所以我写了一篇博客文章,重新审视了没有进口税的隐含(并在一些反馈后再次隐含参数优先).

这是清单:

  • 1)通过本地声明,导入,外部作用域,继承,无前缀可访问的包对象,对当前调用作用域可见.
  • 2)隐式作用域,它包含所有类型的伴随对象和包对象,它们与我们搜索的隐式类型有一些关系(即类型的包对象,类型本身的伴随对象,其类型构造函数,如果有的话)它的参数,如果有的话,还有它的超类型和超级特征).

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

  • 如果您编写一些代码只是定义包,对象,特征和类,并在引用范围时使用它们的字母,则可以改进这一点.根本不需要任何方法声明 - 只是名称和谁扩展谁,以及在哪个范围. (2认同)