Scala在哪里寻找隐式? [英] Where does Scala look for implicits?
问题描述
对Scala的新手来说,一个隐式问题似乎是:编译器在哪里寻找隐式?我的意思是隐含的,因为这个问题似乎从来没有完全形成,就好像没有言语. :-)例如,下面的integral
的值从哪里来?
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)?
例如,scala.Predef
定义了两次从String
的转换:一次转换为WrappedString
,另一次转换为StringOps
.但是,这两个类都共享许多方法,因此,例如在调用map
时,Scala为什么不抱怨歧义?
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.
推荐答案
隐式类型
Scala中的隐式表示可以自动"传递的值,或者是从一种类型到另一种类型的自动转换.
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.
非常简单地谈论后一种类型,如果在类C
的对象o
上调用方法m
,并且该类不支持方法m
,则Scala将寻找隐式类型.从C
到支持m
的东西的转换.一个简单的示例是String
上的map
方法:
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
不支持方法map
,但是StringOps
支持,并且存在从String
到StringOps
的隐式转换(请参见Predef
上的implicit def augmentString
).
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
).
另一种隐式类型是隐式 parameter .它们像其他任何参数一样传递给方法调用,但是编译器会尝试自动填充它们.如果不能,它将抱怨.一个可以显式地传递这些参数,例如,这就是使用breakOut
的方式(请参阅有关breakOut
的问题,在您感到挑战的那天).
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).
在这种情况下,必须声明一个隐式需求,例如foo
方法声明:
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')
方法getIndex
可以接收任何对象,只要存在从其类到Seq[T]
的隐式转换即可.因此,我可以将String
传递给getIndex
,它将起作用.
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.
在后台,编译器将seq.IndexOf(value)
更改为conv(seq).indexOf(value)
.
Behind the scenes, the compiler changes seq.IndexOf(value)
to conv(seq).indexOf(value)
.
这是如此有用,以至于有语法糖来编写它们.使用这种语法糖,可以像这样定义getIndex
:
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)
这种语法糖被描述为 view bound ,类似于 upper bound (CC <: Seq[Int]
)或 lower bound (T >: Null
).
This syntactic sugar is described as a view bound, akin to an upper bound (CC <: Seq[Int]
) or a lower bound (T >: Null
).
隐式参数中的另一个常见模式是 type类模式.这种模式允许为未声明它们的类提供通用接口.它既可以充当桥接模式(获得关注的分离),又可以充当适配器模式.
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.
您提到的Integral
类是类型类模式的经典示例. Scala标准库上的另一个示例是Ordering
.有一个名为Scalaz的图书馆大量使用了这种模式.
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)(_ + _)
}
当仅需要将它们传递到使用它们的其他方法时,
上下文边界会更有用.例如,Seq
上的方法sorted
需要隐式Ordering
.要创建方法reverseSort
,可以编写:
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
由于Ordering[T]
被隐式传递给reverseSort
,因此可以将其隐式传递给sorted
.
Because Ordering[T]
was implicitly passed to reverseSort
, it can then pass it implicitly to sorted
.
当编译器认为需要隐式时,要么是因为您正在调用对象类上不存在的方法,要么是因为您正在调用需要隐式参数的方法时,它将搜索一个隐式参数,该隐式参数将适合需要.
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.
此搜索遵循某些规则,这些规则定义哪些隐式可见,哪些不可见.下表显示了编译器将在何处搜索隐式内容,取自出色的介绍,我衷心推荐给任何想提高其Scala知识的人.从那时起,它得到了反馈和更新的补充.
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.
下面数字1下可用的隐式优先于数字2下的隐式.除此之外,如果有多个与隐式参数类型匹配的合格参数,则将使用静态重载解析规则选择一个最具体的参数(请参见Scala规范§6.26.3).我可以在此答案结尾处链接的问题中找到更详细的信息.
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.
- 首先了解当前范围
- 当前范围内定义的隐式
- 明确进口
- 通配符导入
-
其他文件中的作用域相同
- First look in current scope
- Implicits defined in current scope
- Explicit imports
- wildcard imports
Same scope in other files
- 类型的伴侣对象
- 参数类型的隐式范围(2.9.1)
- 类型参数的隐式范围(2.8.0)
- 嵌套类型的外部对象
- 其他尺寸
- 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:
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)(_ + _)
}
其他文件中的作用域相同
编辑:似乎这没有不同的优先级.如果您有一些示例可以说明优先级区别,请发表评论.否则,请不要依赖于此.
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.
这里有两个值得注意的对象伴侣.首先,研究源"类型的对象伴侣.例如,在对象Option
中有对Iterable
的隐式转换,因此可以在Option
上调用Iterable
方法,或将Option
传递给期望Iterable
的对象.例如:
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)))
但是,List.flatMap
需要一个TraversableOnce
,而Option
不是.然后,编译器在Option
的对象伴侣中查找并找到对Iterable
的转换,该转换为TraversableOnce
,使该表达式正确.
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
方法sorted
采用隐式Ordering
.在这种情况下,它将查找对象Ordering
的内部,该对象是类Ordering
的伴侣,并在其中找到隐式的Ordering[Int]
.
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"
顺便说一下,这就是Scala在问题中发现隐式Numeric[Int]
和Numeric[Long]
的方式,因为它们是在Numeric
而不是Integral
中找到的.
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
.
如果您有一个参数类型为A
的方法,则还将考虑类型为A
的隐式范围. 隐式范围"是指所有这些规则都将递归应用-例如,按照上述规则,将搜索A
的伴随对象以查找隐式.
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.
请注意,这并不意味着将搜索A
的隐式范围以查找该参数的转换,而是查找整个表达式的转换.例如:
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)
从Scala 2.9.1开始可用.
这是使类型类模式真正起作用所必需的.例如,考虑Ordering
:它的伴随对象中带有一些隐式对象,但是您不能向其中添加内容.那么如何为自己的班级创建一个Ordering
呢?
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?
让我们从实施开始:
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
如我们所见,方法sorted
期望使用Ordering[A]
(实际上,它期望使用Ordering[B]
,其中B >: A
). Ordering
中没有任何此类内容,也没有可查看的源"类型.显然,它是在A
内找到的,它是Ordering
的类型参数.
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
.
这也是期望CanBuildFrom
的各种收集方法的工作方式:在CanBuildFrom
的类型参数的伴随对象中找到隐式对象.
This is also how various collection methods expecting CanBuildFrom
work: the implicits are found inside companion objects to the type parameters of CanBuildFrom
.
注意:Ordering
定义为trait Ordering[T]
,其中T
是类型参数.之前,我说过Scala会查看类型参数的内部,这没有多大意义.上面隐式查找的是Ordering[A]
,其中A
是实际类型,而不是类型参数:它是Ordering
的 type参数.请参阅Scala规范的7.2节.
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.
从Scala 2.8.0开始可用.
我实际上还没有看到这样的例子.如果有人可以分享我,我将不胜感激.原理很简单:
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.
编辑
相关的感兴趣的问题:
- Context and view bounds
- Chaining implicits
- Scala: Implicit parameter resolution precedence
这篇关于Scala在哪里寻找隐式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!