对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
?
Note: 这个问题的灵感来自this other question,希望以更一般的方式陈述问题 . 该示例是从那里复制的,因为它在答案中被引用 .
2 回答
含义类型
Scala中的Implicits指的是可以“自动”传递的值,或者可以说是自动转换为从一种类型转换为另一种类型的值 .
隐式转换
简要说一下后一种类型,如果在类
C
的对象o
上调用方法m
,并且该类不支持方法m
,那么Scala将寻找从C
到支持m
的隐式转换 . 一个简单的例子是String
上的map
方法:String
不支持方法map
,但StringOps
支持,并且从String
到StringOps
的隐式转换可用(请参阅implicit def augmentString
Predef
) .隐式参数
另一种隐含的是隐式参数 . 它们像任何其他参数一样传递给方法调用,但编译器会尝试自动填充它们 . 如果不能,它会抱怨 . 可以明确地传递这些参数,例如,人们如何使用
breakOut
(请参阅有关breakOut
的问题,在您感到挑战的那一天) .在这种情况下,必须声明需要隐式,例如
foo
方法声明:查看边界
有一种情况,隐式是隐式转换和隐式参数 . 例如:
方法
getIndex
可以接收任何对象,只要从其类可用到Seq[T]
的隐式转换即可 . 因此,我可以将String
传递给getIndex
,它会起作用 .在幕后,编译器将
seq.IndexOf(value)
更改为conv(seq).indexOf(value)
.这非常有用,有写句法糖 . 使用这种语法糖,
getIndex
可以像这样定义:此语法糖被描述为视图边界,类似于上限(
CC <: Seq[Int]
)或下限(T >: Null
) .上下文边界
隐式参数中的另一种常见模式是类型类模式 . 此模式允许为未声明它们的类提供公共接口 . 它既可以作为桥梁模式 - 获得关注点的分离 - 也可以作为适配器模式 .
您提到的
Integral
类是类型类模式的经典示例 . Scala标准库的另一个例子是Ordering
. 有一个库大量使用这种模式,称为Scalaz .这是它的一个使用示例:
还有它的语法糖,称为上下文绑定,由于需要引用隐式,因此不太有用 . 该方法的直接转换如下所示:
当您只需将它们传递给使用它们的其他方法时,上下文边界更有用 . 例如,
Seq
上的方法sorted
需要隐式Ordering
. 要创建方法reverseSort
,可以写:因为
Ordering[T]
被隐式传递给reverseSort
,所以它可以隐式传递给sorted
.Implicits来自哪里?
当编译器看到需要隐式时,或者因为你正在调用一个在对象类上不存在的方法,或者因为你正在调用一个需要隐式参数的方法,它会搜索一个适合需要的隐式方法 .
此搜索遵循某些规则,这些规则定义哪些隐含可见,哪些不可见 . 下表显示了编译器搜索implicits的位置,取自Josh Suereth关于implicits的优秀_989998_,我衷心向任何想要提高Scala知识的人推荐 . 从那时起,它就得到了补充和反馈 .
下面的数字1下可用的含义优先于数字2下的含义 . 除此之外,如果有几个符合条件的参数与隐式参数的类型匹配,则将使用静态重载分辨率的规则选择最具体的参数(请参阅Scala)规范§6.26.3) . 更多详细信息可以在我在本答复末尾链接的问题中找到 .
首先查看当前范围
在当前范围中定义的Implicits
明确的进口
通配符导入
与其他文件中的范围相同
现在查看相关类型
类型的伴随对象
参数类型的隐含范围 (2.9.1)
类型参数的隐式范围 (2.8.0)
嵌套类型的外部对象
其他尺寸
让我们举一些例子:
在当前范围中定义的含义
明确的进口
通配符进口
其他文件中的相同范围
Edit :似乎没有不同的优先权 . 如果您有一些示例表明优先级区别,请发表评论 . 否则,不要依赖这个 .
这与第一个示例类似,但假设隐式定义与其使用位于不同的文件中 . 另请参阅如何使用package objects来引入含义 .
类型的伴随对象
这里有两个对象伴侣 . 首先,查看"source"类型的对象伴随 . 例如,在对象
Option
内部有一个隐式转换为Iterable
,因此可以在Option
上调用Iterable
方法,或者将Option
传递给期望Iterable
的东西 . 例如:该表达式由编译器翻译为
但是,
List.flatMap
期望TraversableOnce
,Option
不是 . 然后,编译器查找Option
的对象伙伴,并找到转换为Iterable
,即TraversableOnce
,使此表达式正确 .第二,预期类型的伴随对象:
方法
sorted
采用隐式Ordering
. 在这种情况下,它查找对象Ordering
,与类Ordering
的伴侣,并在那里找到隐含的Ordering[Int]
.请注意,还会查看超类的伴随对象 . 例如:
顺便说一下,这就是Scala在你的问题中找到隐含的
Numeric[Int]
和Numeric[Long]
的方式,因为它们是在Numeric
中找到的,而不是Integral
.参数类型的隐含范围
如果您有一个参数类型为
A
的方法,那么也将考虑类型为A
的隐式范围 . "implicit scope"我的意思是所有这些规则都将以递归方式应用 - 例如,根据上述规则,将搜索A
的伴随对象的含义 .请注意,这并不意味着将搜索
A
的隐式范围以查找该参数的转换,而是搜索整个表达式的转换 . 例如:This is available since Scala 2.9.1.
类型参数的隐式范围
这是使类型类模式真正起作用所必需的 . 例如,考虑
Ordering
:它的伴随对象中有一些含义,但你不能添加东西 . 那么如何为自己的类自动找到Ordering
?让我们从实施开始:
所以,考虑一下你打电话时会发生什么
正如我们所看到的,方法
sorted
期望Ordering[A]
(实际上,它期望Ordering[B]
,其中B >: A
) .Ordering
中没有任何此类内容,并且没有"source"类型可供查看 . 显然,它是在A
中找到它,这是Ordering
的类型参数 .这也是各种收集方法期望
CanBuildFrom
工作的方式:在伴随对象中找到CanBuildFrom
的类型参数的含义 .Note :
Ordering
定义为trait Ordering[T]
,其中T
是类型参数 . 以前,我说Scala查看了内部类型参数,这没有多大意义 . 上面隐含的是Ordering[A]
,其中A
是实际类型,而不是类型参数:它是Ordering
的类型参数 . 请参阅Scala规范的第7.2节 .This is available since Scala 2.8.0.
嵌套类型的外部对象
我实际上没有看过这个例子 . 如果有人可以分享,我将不胜感激 . 原理很简单:
其他尺寸
我很确定这是一个笑话,但这个答案可能不是最新的 . 因此,不要将此问题视为正在发生的事情的最终仲裁者,如果您确实注意到它已经过时,请通知我,以便我可以解决它 .
EDIT
相关问题:
Context and view bounds
Chaining implicits
Scala: Implicit parameter resolution precedence
我想找出隐式参数解析的优先级,而不仅仅是找到它的位置,所以我写了一篇博文revisiting implicits without import tax(和一些反馈后的implicit parameter precedence again) .
这是列表:
1)通过可以无前缀访问的本地声明,导入,外部作用域,继承,包对象,对当前调用作用域可见 .
2)隐式作用域,它包含所有类型的伴随对象和包对象,它们与我们搜索的隐式类型有某种关系(即包类型的对象,类型本身的伴随对象,其类型构造函数(如果有),其参数(如果有),以及它的超类型和超级类型) .
如果在任一阶段我们发现多个隐式,则使用静态重载规则来解决它 .