斯卡拉在哪里寻找隐含?

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时不会抱怨模糊性呢?

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


隐含类型

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

隐式转换

对于后一种类型非常简要地说,如果在类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]的隐式转换即可。 正因为如此,我可以传递一个StringgetIndex ,它会起作用。

在幕后,编译器将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)(_ + _)
}

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

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

由于Ordering[T]被隐式传递给reverseSort ,因此它可以将其隐式传递给sorted

Implicits从哪里来?

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

这种搜索遵循某些规则来定义哪些隐含是可见的,哪些不是。 下面的表格显示了编译器在何处搜索implicits是从Josh Suereth关于暗示的精彩演示中获得的,我衷心向任何希望提高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)))
    

    但是, List.flatMap需要一个TraversableOnce ,该Option不是。 然后,编译器在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也将予以考虑。 对于“隐式范围”,我的意思是所有这些规则都将递归地应用 - 例如,根据上述规则, 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开始提供的。

    类型参数的隐式范围

    这是使类型模式真正起作用所必需的。 例如,考虑Ordering :它的伴侣对象中带有一些含义,但不能添加任何东西。 那么如何为自己的班级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没有这样的东西,并且没有“源”类型可供查看。 显然,它在A找到它,这是Ordering一个类型参数。

    这也是期望CanBuildFrom工作的各种收集方法的方式:隐式在伴随对象内部找到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"
    

    其他维度

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

    编辑

    感兴趣的相关问题:

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

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

    列表如下:

  • 1)通过本地声明,导入,外部作用域,继承,无需前缀即可访问的包对象,使当前调用范围可见。
  • 2)隐式作用域,其中包含所有类型的伴侣对象和包对象,它们与我们搜索的隐式类型有一定的关系(即类型的包对象,类型本身的伴随对象,类型构造器(如果有的话),其参数(如果有的话),以及它的超类型和超类型)。
  • 如果在任一阶段我们发现多个隐式静态重载规则用于解决它。

    链接地址: http://www.djcxy.com/p/72813.html

    上一篇: Where does Scala look for implicits?

    下一篇: What is the Scala identifier "implicitly"?