Scala在哪里寻找隐含的东西?
Scala的新手隐含的问题似乎是:编译器在哪里查找implicits? 我的意思是隐含的,因为这个问题似乎从来没有完全形成,好像没有文字。 :-)例如,下面的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
另一个决定学习第一个问题的答案的问题是编译器如何select使用哪种隐含的,在某些明显不明确的情况下(但无论如何编译)?
例如, scala.Predef
定义了两个String
转换:一个转换为WrappedString
,另一个转换为StringOps
。 然而,这两个类都有很多方法,所以为什么Scala在调用map
时不会抱怨模糊的问题呢?
注:这个问题受到另一个问题的启发,希望以更一般的方式说明问题。 这个例子是从那里复制的,因为它在答案中被引用。
隐含types
在Scala中的含义是指一个可以自动传递的值,也就是说,可以自动传递一个types到另一个types的值。
隐式转换
简单地说一下后一种types,如果在类C
一个对象o
上调用一个方法m
,并且该类不支持方法m
,那么Scala将查找从C
到隐式的转换,支持m
。 一个简单的例子就是String
上的方法map
:
"abc".map(_.toInt)
String
不支持方法map
,但是StringOps
支持,并且存在从String
到StringOps
的隐式转换(请参阅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]
的隐式转换即可。 正因为如此,我可以通过一个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
)。
上下文界限
隐式参数中的另一个常见模式是types模式 。 这种模式可以为没有声明它们的类提供通用的接口。 它既可以作为桥梁模式 – 获得问题的分离 – 也可以作为适配器模式。
您提到的Integral
类是类类模式的经典示例。 Scala标准库上的另一个例子是Ordering
。 有一个图书馆,大量使用这种模式,称为斯卡拉斯。
这是一个使用的例子:
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
。 要创build一个方法reverseSort
,可以写:
def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse
因为Ordering[T]
被隐式地传递给了reverseSort
,所以它可以将它隐式地传递给sorted
。
Implicits从哪里来?
当编译器认为需要隐式的时候,要么是因为你调用的对象的类不存在的方法,要么是因为你正在调用一个需要一个隐式参数的方法,它会search一个隐含的, 。
这种search服从某些规则,这些规则定义哪些隐含是可见的,哪些不是。 下面的表格显示了编译器在何处searchimplicits是从一个关于Josh Suereth的暗示的精彩演示中获得的,我衷心推荐给那些希望改进Scala知识的人。 自那时以来,它一直得到反馈和更新的补充。
在下面的编号1下可用的含义优先于在编号2下的含义。除此之外,如果有几个符合隐式参数types的符合条件的参数,将使用静态重载parsing规则select最具体的参数(参见Scala规范§6.26.3)。 更详细的信息可以在我在这个答案的结尾处链接到的问题中find。
- 首先看当前的范围
- 在当前范围内定义的含义
- 显式导入
- 通配符导入
-
其他文件中的范围相同
- 现在看看关联的types
- types的伴随对象
- 参数types的隐含范围(2.9.1)
- types参数的隐式范围(2.8.0)
- 嵌套types的外部对象
- 其他维度
我们来举个例子吧:
在当前范围内定义的含义
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)(_ + _) }
同样的范围在其他文件
编辑 :这似乎没有不同的优先顺序。 如果你有一些例子说明了优先区分,请发表评论。 否则,不要依赖这个。
这就像第一个例子,但假设隐式定义与其用法不同的文件。 另请参阅包对象可能如何用于引入含义。
types的伴侣对象
这里有两个对象的注意事项。 首先,研究“源”types的对象伴侣。 例如,在对象Option
有一个隐式转换为Iterable
,因此可以在Option
上调用Iterable
方法,或者将Option
传递给期望Iterable
东西。 例如:
for { x <- List(1, 2, 3) y <- Some('x') } yield, (x, y)
该expression式被编译器翻译为
List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))
但是, List.flatMap
需要一个TraversableOnce
,哪个Option
不是。 然后,编译器在Option
的对象伴侣内查找并find转换为Iterable
(这是TraversableOnce
,从而使该expression式正确。
其次,预期types的伴侣对象:
List(1, 2, 3).sorted
sorted
的方法采用隐式Ordering
。 在这种情况下,它在对象Ordering
查找,伴随着Ordering
类,并在那里find一个隐式的Ordering[Int]
。
请注意,超级类的伴侣对象也被调查。 例如:
class A(val n: Int) object A { implicit def str(a: A) = "A: %d" format an } class B(val x: Int, y: Int) extends A(y) val b = new B(5, 2) val s: String = b // s == "A: 2"
这就是Scala如何在你的问题中find暗含的Numeric[Int]
和Numeric[Long]
,顺便说一句,因为它们是在Numeric
中find的,而不是Integral
。
一个参数types的隐式范围
如果你有一个参数types为A
的方法,那么typesA
的隐式范围也将被考虑。 所谓“隐式范围”,是指所有这些规则将被recursion地应用 – 例如,根据上面的规则, A
的伴随对象将被search隐含。
请注意,这并不意味着A
的隐式范围将被search该参数的转换,而是整个expression式的转换。 例如:
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)
这是从斯卡拉2.9.1以来。
types参数的隐式范围
这是使types模式真正起作用所必需的。 考虑Ordering
,例如:它的伴侣对象带有一些暗示,但是你不能添加东西。 那么怎样才能自动find自己的class级呢?
让我们从实施开始:
class A(val n: Int) object A { implicit val ord = new Ordering[A] { def compare(x: A, y: A) = implicitly[Ordering[Int]].compare(xn, yn) } }
所以,考虑一下你打电话时会发生什么
List(new A(5), new A(2)).sorted
正如我们所看到的, sorted
的方法需要一个Ordering[A]
(实际上,它期望一个Ordering[B]
,其中B >: A
)。 Ordering
没有这样的东西,并且没有“源”types。 显然,它是在A
findA
,这是Ordering
一个types参数 。
这也是期望CanBuildFrom
各种各样的收集方法的工作原理:在伴随对象内部findCanBuildFrom
在CanBuildFrom
types参数中的CanBuildFrom
。
注 : Ordering
被定义为trait Ordering[T]
,其中T
是一个types参数。 以前我说过Scala看内部types参数,这没什么意义。 上面的隐式查找是Ordering[A]
,其中A
是实际types,而不是types参数:它是Ordering
的types参数 。 参见Scala规范的7.2节。
这是自Scala 2.8.0以来。
嵌套types的外部对象
我还没有看到这个例子。 如果有人能分享一个,我将不胜感激。 原则很简单:
class A(val n: Int) { class B(val m: Int) { require(m < n) } } object A { implicit def bToString(b: A#B) = "B: %d" format bm } val a = new A(5) val b = new aB(3) val s: String = b // s == "B: 3"
其他维度
我很确定这是一个笑话,但这个答案可能不是最新的。 所以,不要把这个问题当作最后的仲裁者,如果你注意到它已经过时了,请告诉我,以便我可以修复它。
编辑
感兴趣的相关问题:
- 上下文和视图边界
- 链接牵连
- Scala:隐式参数parsing优先
我想知道隐式参数parsing的优先级,而不仅仅是它在哪里寻找,所以我写了一篇博客文章, 重新回顾implicits没有import税 (一些反馈后再次隐式参数优先 )。
列表如下:
- 1)通过本地声明,导入,外部作用域,inheritance,可以不带前缀访问的包对象,对当前调用范围隐含可见。
- 2) 隐式作用域 ,它包含所有types的伴随对象和包对象,这些对象和包对象与我们search的隐式types有一定的关系(即types的包对象,types本身的伴随对象,types构造器(如果有的话),其参数(如果有的话),还有它的超types和超types)。
如果在任一阶段我们发现多个隐式静态重载规则被用来解决它。