Scala 在哪里寻找隐式? [英] Where does Scala look for implicits?

查看:35
本文介绍了Scala 在哪里寻找隐式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于 Scala 新手来说,一个隐式问题似乎是:编译器在哪里寻找隐式?我的意思是隐含的,因为这个问题似乎从来没有完全形成,好像没有词可以形容.:-) 例如,下面 integral 的值从何而来?

scala>导入 scala.math._导入 scala.math._标度>def foo[T](t: T)(隐式积分:Integral[T]) {println(integral)}foo: [T](t: T)(隐式积分:scala.math.Integral[T])单位标度>富(0)scala.math.Numeric$IntIsIntegral$@3dbea611标度>富(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 支持,并且有一个从 String 的隐式转换到 StringOps 可用(参见 Predef 上的 implicit def gainString).

隐式参数

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

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

def foo[T](t: T)(隐式积分: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])(隐式积分:Integral[T]): T = {导入积分._//将有问题的隐式放入范围list.foldLeft(integral.zero)(_ + _)}

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

def sum[T : Integral](list: List[T]): T = {val 积分 = 隐式 [Integral[T]]导入积分._//将有问题的隐式放入范围list.foldLeft(integral.zero)(_ + _)}

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

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

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

隐式来自哪里?

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

此搜索遵循某些规则,这些规则定义了哪些隐式可见,哪些不可见.下表显示了编译器将在何处搜索隐式值取自出色的 演示 关于隐式的 Josh Suereth,我衷心推荐给任何想要提高他们的 Scala 知识的人.从那时起,它得到了反馈和更新的补充.

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

  1. 首先查看当前范围
    • 在当前范围内定义的隐式
    • 显式导入
    • 通配符导入
    • 其他文件中的相同作用域
  2. 现在查看关联类型
    • 一种类型的伴随对象
    • 参数类型的隐式作用域(2.9.1)
    • 类型参数的隐式作用域(2.8.0)
    • 嵌套类型的外部对象
    • 其他尺寸

让我们为他们举一些例子:

在当前范围内定义的隐式

隐式 val n: Int = 5def add(x: Int)(隐式 y: Int) = x + yadd(5)//从当前作用域中取出 n

显式导入

import scala.collection.JavaConversions.mapAsScalaMapdef env = System.getenv()//Java 映射val term = env("TERM")//从 Java Map 到 Scala Map 的隐式转换

通配符导入

def sum[T : Integral](list: List[T]): T = {val 积分 = 隐式 [Integral[T]]导入积分._//将有问题的隐式放入范围list.foldLeft(integral.zero)(_ + _)}

其他文件中的相同范围

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

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

一种类型的伴随对象

这里有两个值得注意的对象伙伴.首先,查看源"类型的对象伴侣.例如,在对象Option 中,有一个到Iterable 的隐式转换,因此可以在Option 上调用Iterable 方法>,或者将 Option 传递给需要 Iterable 的东西.例如:

for {x <- 列表(1, 2, 3)y <- 一些('x')}产量(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)对象 A {隐式 def str(a: A) = "A: %d" 格式 a.n}类 B(val x: Int, y: Int) 扩展 A(y)val b = 新 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)}对象 A {隐式 def fromInt(n: Int) = new A(n)}//这成为可能:1 + 新 A(1)//因为它被转换成这样:A.fromInt(1) + 新的 A(1)

此功能自 Scala 2.9.1 起可用.

类型参数的隐式作用域

这是使类型类模式真正起作用所必需的.以 Ordering 为例:它在它的伴生对象中带有一些隐式,但你不能向它添加东西.那么你如何为你自己的类创建一个自动找到的Ordering?

让我们从实现开始:

class A(val n: Int)对象 A {隐式 val ord = 新排序 [A] {def compare(x: A, y: A) = 隐式[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 是实际类型,而不是类型参数:它是类型参数到<代码>订购.请参阅 Scala 规范的第 7.2 节.

此功能自 Scala 2.8.0 起可用.

嵌套类型的外部对象

我还没有真正看到过这样的例子.如果有人可以分享一个,我将不胜感激.原理很简单:

class A(val n: Int) {类 B(val m: Int) { require(m 

其他尺寸

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

编辑

感兴趣的相关问题:

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.

解决方案

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 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:

这篇关于Scala 在哪里寻找隐式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆