gpt4 book ai didi

scala - Scala 在哪里寻找隐式?

转载 作者:行者123 更新时间:2023-12-03 03:56:57 25 4
gpt4 key购买 nike

对于 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 时不提示歧义? ?

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

最佳答案

隐式类型

Scala 中的隐式指的是可以“自动”传递的值,可以这么说,或者是自动进行从一种类型到另一种类型的转换。

隐式转换

简单说一下后一种类型,如果调用一个方法m在对象上 o类(class)C ,并且该类不支持方法 m ,那么 Scala 将寻找来自 C 的隐式转换到确实支持的东西 m .一个简单的例子是方法 mapString :

"abc".map(_.toInt)
String不支持该方法 map ,但是 StringOps确实如此,而且还有来自 String 的隐式转换至 StringOps可用(请参阅 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)

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

上下文边界

隐式参数中的另一个常见模式是类型类模式。这种模式可以为没有声明它们的类提供公共(public)接口(interface)。它既可以用作桥接模式(获得关注点分离),也可以用作适配器模式。
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)(_ + _)
}

它也有语法糖,称为上下文绑定(bind),由于需要引用隐式而变得不太有用。该方法的直接转换如下所示:
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)(_ + _)
}

当您只需要将它们传递给使用它们的其他方法时,上下文边界更有用。例如,方法 sortedSeq需要一个隐含的 Ordering .创建方法 reverseSort ,可以这样写:
def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse

因为 Ordering[T]被隐式传递给 reverseSort ,然后可以将其隐式传递给 sorted .

隐式来自哪里?

当编译器发现需要隐式参数时,要么是因为您正在调用对象的类中不存在的方法,要么是因为您正在调用需要隐式参数的方法,它将搜索适合需要的隐式参数.

此搜索遵循某些规则,这些规则定义了哪些隐式可见,哪些不可见。下表显示了编译器将在哪里搜索隐式值取自出色的 presentation关于 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)(_ + _)
    }

    其他文件中的相同范围

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

    这与第一个示例类似,但假设隐式定义与其用法位于不同的文件中。另见如何 package objects可能用于引入隐式。

    类型的伴随对象

    这里有两个值得注意的对象伴侣。首先,查看“源”类型的对象伴侣。例如,在对象内部 Option存在到 Iterable 的隐式转换,所以可以拨打 Iterable Option 上的方法,或通过 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 , 类(class)同伴 Ordering ,并找到一个隐含的 Ordering[Int]那里。

    请注意,还研究了父类(super class)的伴生对象。例如:
    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为您自己的类(class)自动找到?

    让我们从实现开始:
    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"

    其他尺寸

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

    编辑

    感兴趣的相关问题:
  • Context and view bounds
  • Chaining implicits
  • Scala: Implicit parameter resolution precedence
  • 关于scala - Scala 在哪里寻找隐式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5598085/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com