Scala 在哪里寻找隐式?

发布于 2024-10-31 05:56:53 字数 930 浏览 1 评论 0原文

对于 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 时不抱怨歧义呢?

注意:这个问题的灵感来自另一个问题< /a>,希望以更一般的方式陈述问题。该示例是从那里复制的,因为答案中提到了它。

An implicit question to newcomers to Scala seems to be: where does the compiler look for implicits? I mean implicit because the question never seems to get fully formed, as if there weren't words for it. :-) For example, where do the values for integral below come from?

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

Another question that does follow up to those who decide to learn the answer to the first question is how does the compiler choose which implicit to use, in certain situations of apparent ambiguity (but that compile anyway)?

For instance, scala.Predef defines two conversions from String: one to WrappedString and another to StringOps. Both classes, however, share a lot of methods, so why doesn't Scala complain about ambiguity when, say, calling map?

Note: this question was inspired by this other question, in the hopes of stating the problem in a more general manner. The example was copied from there, because it is referred to in the answer.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

泪冰清 2024-11-07 05:56:53

隐式类型

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

隐式转换

非常简单地谈论后一种类型,如果有人在类 C 的对象 o 上调用方法 m,并且该类执行以下操作:不支持方法 m,那么 Scala 将寻找从 C支持支持 m 的隐式转换。一个简单的例子是 String 上的 map 方法:

"abc".map(_.toInt)

String 不支持 map 方法,但是 >StringOps 确实如此,并且可以从 StringStringOps 进行隐式转换(请参阅 Predef< 上的implicit def AugmentString) /代码>)。

隐式参数

另一种隐式参数是隐式参数。这些参数像任何其他参数一样传递给方法调用,但编译器会尝试自动填充它们。如果做不到,就会抱怨。 可以显式传递这些参数,例如,这就是使用 breakOut 的方式(请参阅有关 breakOut 的问题,在您心情不好的一天来挑战)。

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

def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}

View Bounds

有一种情况,隐式既是隐式转换又是隐式参数。例如:

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)

此语法糖被描述为视图边界,类似于上限(< code>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,因此它可以将其隐式传递给 <代码>排序。

隐式从哪里来?

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

此搜索遵循某些规则,这些规则定义了哪些隐式可见,哪些不可见。下表显示了编译器将在何处搜索隐式内容,取自关于隐式内容的精彩 演示(时间戳 20:20)作者是 Josh Suereth,我衷心向所有想要提高 Scala 知识的人推荐这本书。从那时起,它就得到了反馈和更新的补充。

下面第 1 项下可用的隐式优先于第 2 项下的隐式。除此之外,如果有多个符合隐式参数类型的参数,则将使用静态重载解析规则选择一个最具体的参数(请参阅 Scala规范第 §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

显式导入

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 中找到的,而不是整体

参数类型的隐式作用域

如果您有一个参数类型为 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 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] (实际上,它需要一个 排序[B],其中B >:A)。 Ordering 中没有任何这样的东西,并且没有可供查看的“源”类型。显然,它在 A 中找到它,它是 Ordering类型参数

这也是各种需要 CanBuildFrom 的集合方法的工作方式:在 CanBuildFrom 的类型参数的伴生对象中找到隐式。

注意Ordering 定义为trait Ordering[T],其中T 是类型参数。之前,我说过 Scala 查看类型参数的内部,这没有多大意义。上面查找的隐式是 Ordering[A],其中 A 是实际类型,而不是类型参数:它是 类型参数代码>排序。请参阅 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"

其他维度

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

编辑

感兴趣的相关问题:

Types of Implicits

Implicits in Scala refers to either a value that can be passed "automatically", so to speak, or a conversion from one type to another that is made automatically.

Implicit Conversion

Speaking very briefly about the latter type, if one calls a method m on an object o of a class C, and that class does not support method m, then Scala will look for an implicit conversion from C to something that does support m. A simple example would be the method map on String:

"abc".map(_.toInt)

String does not support the method map, but StringOps does, and there's an implicit conversion from String to StringOps available (see implicit def augmentString on Predef).

Implicit Parameters

The other kind of implicit is the implicit parameter. These are passed to method calls like any other parameter, but the compiler tries to fill them in automatically. If it can't, it will complain. One can pass these parameters explicitly, which is how one uses breakOut, for example (see question about breakOut, on a day you are feeling up for a challenge).

In this case, one has to declare the need for an implicit, such as the foo method declaration:

def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}

View Bounds

There's one situation where an implicit is both an implicit conversion and an implicit parameter. For example:

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

getIndex("abc", 'a')

The method getIndex can receive any object, as long as there is an implicit conversion available from its class to Seq[T]. Because of that, I can pass a String to getIndex, and it will work.

Behind the scenes, the compiler changes seq.IndexOf(value) to conv(seq).indexOf(value).

This is so useful that there is syntactic sugar to write them. Using this syntactic sugar, getIndex can be defined like this:

def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)

This syntactic sugar is described as a view bound, akin to an upper bound (CC <: Seq[Int]) or a lower bound (T >: Null).

Context Bounds

Another common pattern in implicit parameters is the type class pattern. This pattern enables the provision of common interfaces to classes which did not declare them. It can both serve as a bridge pattern -- gaining separation of concerns -- and as an adapter pattern.

The Integral class you mentioned is a classic example of type class pattern. Another example on Scala's standard library is Ordering. There's a library that makes heavy use of this pattern, called Scalaz.

This is an example of its use:

def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
    import integral._   // get the implicits in question into scope
    list.foldLeft(integral.zero)(_ + _)
}

There is also syntactic sugar for it, called a context bound, which is made less useful by the need to refer to the implicit. A straight conversion of that method looks like this:

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

Context bounds are more useful when you just need to pass them to other methods that use them. For example, the method sorted on Seq needs an implicit Ordering. To create a method reverseSort, one could write:

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

Because Ordering[T] was implicitly passed to reverseSort, it can then pass it implicitly to sorted.

Where do Implicits come from?

When the compiler sees the need for an implicit, either because you are calling a method which does not exist on the object's class, or because you are calling a method that requires an implicit parameter, it will search for an implicit that will fit the need.

This search obey certain rules that define which implicits are visible and which are not. The following table showing where the compiler will search for implicits was taken from an excellent presentation (timestamp 20:20) about implicits by Josh Suereth, which I heartily recommend to anyone wanting to improve their Scala knowledge. It has been complemented since then with feedback and updates.

The implicits available under number 1 below has precedence over the ones under number 2. Other than that, if there are several eligible arguments which match the implicit parameter’s type, a most specific one will be chosen using the rules of static overloading resolution (see Scala Specification §6.26.3). More detailed information can be found in a question I link to at the end of this answer.

  1. First look in current scope
    • Implicits defined in current scope
    • Explicit imports
    • wildcard imports
    • Same scope in other files
  2. Now look at associated types in
    • Companion objects of a type
    • Implicit scope of an argument's type (2.9.1)
    • Implicit scope of type arguments (2.8.0)
    • Outer objects for nested types
    • Other dimensions

Let's give some examples for them:

Implicits Defined in Current Scope

implicit val n: Int = 5
def add(x: Int)(implicit y: Int) = x + y
add(5) // takes n from the current scope

Explicit Imports

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

Wildcard Imports

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

Same Scope in Other Files

Edit: It seems this does not have a different precedence. If you have some example that demonstrates a precedence distinction, please make a comment. Otherwise, don't rely on this one.

This is like the first example, but assuming the implicit definition is in a different file than its usage. See also how package objects might be used in to bring in implicits.

Companion Objects of a Type

There are two object companions of note here. First, the object companion of the "source" type is looked into. For instance, inside the object Option there is an implicit conversion to Iterable, so one can call Iterable methods on Option, or pass Option to something expecting an Iterable. For example:

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

That expression is translated by the compiler to

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

However, List.flatMap expects a TraversableOnce, which Option is not. The compiler then looks inside Option's object companion and finds the conversion to Iterable, which is a TraversableOnce, making this expression correct.

Second, the companion object of the expected type:

List(1, 2, 3).sorted

The method sorted takes an implicit Ordering. In this case, it looks inside the object Ordering, companion to the class Ordering, and finds an implicit Ordering[Int] there.

Note that companion objects of super classes are also looked into. For example:

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"

This is how Scala found the implicit Numeric[Int] and Numeric[Long] in your question, by the way, as they are found inside Numeric, not Integral.

Implicit Scope of an Argument's Type

If you have a method with an argument type A, then the implicit scope of type A will also be considered. By "implicit scope" I mean that all these rules will be applied recursively -- for example, the companion object of A will be searched for implicits, as per the rule above.

Note that this does not mean the implicit scope of A will be searched for conversions of that parameter, but of the whole expression. For example:

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.

Implicit Scope of Type Arguments

This is required to make the type class pattern really work. Consider Ordering, for instance: It comes with some implicits in its companion object, but you can't add stuff to it. So how can you make an Ordering for your own class that is automatically found?

Let's start with the implementation:

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)
    }
}

So, consider what happens when you call

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

As we saw, the method sorted expects an Ordering[A] (actually, it expects an Ordering[B], where B >: A). There isn't any such thing inside Ordering, and there is no "source" type on which to look. Obviously, it is finding it inside A, which is a type argument of Ordering.

This is also how various collection methods expecting CanBuildFrom work: the implicits are found inside companion objects to the type parameters of CanBuildFrom.

Note: Ordering is defined as trait Ordering[T], where T is a type parameter. Previously, I said that Scala looked inside type parameters, which doesn't make much sense. The implicit looked for above is Ordering[A], where A is an actual type, not type parameter: it is a type argument to Ordering. See section 7.2 of the Scala specification.

This is available since Scala 2.8.0.

Outer Objects for Nested Types

I haven't actually seen examples of this. I'd be grateful if someone could share one. The principle is simple:

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"

Other Dimensions

I'm pretty sure this was a joke, but this answer might not be up-to-date. So don't take this question as being the final arbiter of what is happening, and if you do noticed it has gotten out-of-date, please inform me so that I can fix it.

EDIT

Related questions of interest:

梦晓ヶ微光ヅ倾城 2024-11-07 05:56:53

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

列表如下:

  • 1) 通过本地声明、导入、外部作用域、继承、无需前缀即可访问的包对象对当前调用作用域可见的隐式。
  • 2)隐式作用域,它包含与我们搜索的隐式类型有某种关系的各种伴生对象和包对象(即类型的包对象,类型本身的伴生对象,它的类型构造函数(如果有)、它的参数(如果有)以及它的超类型和超特征)。

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

I wanted to find out the precedence of the implicit parameter resolution, not just where it looks for, so I wrote a blog post revisiting implicits without import tax (and implicit parameter precedence again after some feedback).

Here's the list:

  • 1) implicits visible to current invocation scope via local declaration, imports, outer scope, inheritance, package object that are accessible without prefix.
  • 2) implicit scope, which contains all sort of companion objects and package object that bear some relation to the implicit's type which we search for (i.e. package object of the type, companion object of the type itself, of its type constructor if any, of its parameters if any, and also of its supertype and supertraits).

If at either stage we find more than one implicit, static overloading rule is used to resolve it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文