首页 文章

Scala在哪里寻找暗示?

提问于
浏览
375

对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

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

例如, scala.Predef 定义了从 String 的两次转换:一次转换为 WrappedString ,另一次转换为 StringOps . 但是,这两个类都有很多方法,所以为什么Scala不会抱怨模糊,比如说 map

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

2 回答

  • 24

    含义类型

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

    隐式转换

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

    "abc".map(_.toInt)
    

    String 不支持方法 map ,但 StringOps 支持,并且从 StringStringOps 的隐式转换可用(请参阅 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 ) .

    上下文边界

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

    您提到的 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)(_ + _)
    }
    

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

    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 上的方法 sorted 需要隐式 Ordering . 要创建方法 reverseSort ,可以写:

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

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

    Implicits来自哪里?

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

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

    下面的数字1下可用的含义优先于数字2下的含义 . 除此之外,如果有几个符合条件的参数与隐式参数的类型匹配,则将使用静态重载分辨率的规则选择最具体的参数(请参阅Scala)规范§6.26.3) . 更多详细信息可以在我在本答复末尾链接的问题中找到 .

    • 首先查看当前范围

    • 在当前范围中定义的Implicits

    • 明确的进口

    • 通配符导入

    • 与其他文件中的范围相同

    • 现在查看相关类型

    • 类型的伴随对象

    • 参数类型的隐含范围 (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)(_ + _)
    }
    

    其他文件中的相同范围

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

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

    类型的伴随对象

    这里有两个对象伴侣 . 首先,查看"source"类型的对象伴随 . 例如,在对象 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)))
    

    但是, List.flatMap 期望 TraversableOnceOption 不是 . 然后,编译器查找 Option 的对象伙伴,并找到转换为 Iterable ,即 TraversableOnce ,使此表达式正确 .

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

    List(1, 2, 3).sorted
    

    方法 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"
    

    顺便说一下,这就是Scala在你的问题中找到隐含的 Numeric[Int]Numeric[Long] 的方式,因为它们是在 Numeric 中找到的,而不是 Integral .

    参数类型的隐含范围

    如果您有一个参数类型为 A 的方法,那么也将考虑类型为 A 的隐式范围 . "implicit scope"我的意思是所有这些规则都将以递归方式应用 - 例如,根据上述规则,将搜索 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)
    

    This is available since 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)
        }
    }
    

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

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

    正如我们所看到的,方法 sorted 期望 Ordering[A] (实际上,它期望 Ordering[B] ,其中 B >: A ) . Ordering 中没有任何此类内容,并且没有"source"类型可供查看 . 显然,它是在 A 中找到它,这是 Ordering 的类型参数 .

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

    NoteOrdering 定义为 trait Ordering[T] ,其中 T 是类型参数 . 以前,我说Scala查看了内部类型参数,这没有多大意义 . 上面隐含的是 Ordering[A] ,其中 A 是实际类型,而不是类型参数:它是 Ordering 的类型参数 . 请参阅Scala规范的第7.2节 .

    This is available since 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

    相关问题:

  • 531

    我想找出隐式参数解析的优先级,而不仅仅是找到它的位置,所以我写了一篇博文revisiting implicits without import tax(和一些反馈后的implicit parameter precedence again) .

    这是列表:

    • 1)通过可以无前缀访问的本地声明,导入,外部作用域,继承,包对象,对当前调用作用域可见 .

    • 2)隐式作用域,它包含所有类型的伴随对象和包对象,它们与我们搜索的隐式类型有某种关系(即包类型的对象,类型本身的伴随对象,其类型构造函数(如果有),其参数(如果有),以及它的超类型和超级类型) .

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

相关问题