这个定义了类型类和基本的类型类实例:
package com.netgents.typeclass.hole case class Rabbit trait Hole[A] { def findHole(x: A): String } object Hole { def apply[A: Hole] = implicitly[Hole[A]] implicit val rabbitHoleInHole = new Hole[Rabbit] { def findHole(x: Rabbit) = "Rabbit found the hole in Hole companion object" } }
这是包对象:
package com.netgents.typeclass package object hole { def findHole[A: Hole](x: A) = Hole[A].findHole(x) implicit val rabbitHoleInHolePackage = new Hole[Rabbit] { def findHole(x: Rabbit) = "Rabbit found the hole in Hole package object" } }
这是测试:
package com.netgents.typeclass.hole object Test extends App { implicit val rabbitHoleInOuterTest = new Hole[Rabbit] { def findHole(x: Rabbit) = "Rabbit found the hole in outer Test object" } { implicit val rabbitHoleInInnerTest = new Hole[Rabbit] { def findHole(x: Rabbit) = "Rabbit found the hole in inner Test object" } println(findHole(Rabbit())) } }
如您所见,Hole是一个简单的类型类,它定义了Rabbit试图找到的方法.我试图找出隐含的解决方案规则.
>所有四个类型类实例都没有注释,scalac抱怨rabbitHoleInHolePackage和rabbitHoleInHole的含糊不清. (为什么?)
>如果我评论出RabbitHoleInHole,scalac编译并且我回来了“兔子在Hole包对象中找到了洞”. (不应该在本地范围内隐含优先权吗?)
>如果我随后注释掉了RabbitHoleInHolePackage,scalac会抱怨rabbitHoleInOuterTest和rabbitHoleInInnerTest的含糊不清. (为什么?在eed3si9n的文章中,下面列出了url,他发现内含和外部范围的含义可能有不同的优先权.)
>如果我然后注释掉RabbitHoleInInnerTest,scalac编译并且我回来了“兔子在外部测试对象中找到了洞”.
正如您所看到的,上述行为并未遵循我在隐式解析中阅读的规则.我只描述了你在评论/取消注释实例时可以做的一小部分组合,其中大多数确实非常奇怪 – 我还没有进入导入和子类.
我已经阅读并观看了presentation by suereth,stackoverflow answer by sobral和a very elaborate revisit by eed3si9n,但我仍然感到困惑.
解决方法
package rabbit { trait TC object Test extends App { implicit object testInstance1 extends TC { override def toString = "test1" } { implicit object testInstance2 extends TC { override def toString = "test2" } println(implicitly[TC]) } } }
Scalac查找范围内的任何含义,找到testInstance1和testInstance2.一个人在一个更紧凑的范围内这一事实只有在它们具有相同名称时才有意义 – 正常的阴影规则适用.我们选择了不同的名称,并且没有隐含的比另一个更具体,因此正确报告了歧义.
让我们尝试另一个例子,这次我们将在本地范围内对照包对象中的一个隐含.
package rabbit { object `package` { implicit object packageInstance extends TC { override def toString = "package" } } trait TC object Test extends App { { implicit object testInstance2 extends TC { override def toString = "test2" } println(implicitly[TC]) } } }
这里发生了什么?与之前一样,隐式搜索的第一阶段考虑了呼叫站点范围内的所有隐含.在这种情况下,我们有testInstance2和packageInstance.这些是模糊的,但在报告该错误之前,第二阶段开始,并搜索TC的隐含范围.
但这里的隐含范围是什么? TC甚至没有配套对象?我们需要在Scala Reference的7.2中查看精确的定义.
The implicit scope of a type T consists of all companion modules
(§5.4) of classes that are associated with the implicit parameter’s
type. Here,we say a class C is associated with a type T,if it
is a base class (§5.1.2) of some part of T.The parts of a type
T
are:
- if
T
is a compound typeT1 with ... with Tn
,
the union of the parts ofT1,...,Tn
,as well asT
itself,- if
T
is a parameterized typeS[T1,Tn]
,the union of the parts ofS
andT1,
- if
T
is a singleton typep.type
,the parts of the type ofp
,- if
T
is a type projectionS#U
,the parts ofS
as well asT
itself,- in all other cases,just
T
itself.
我们正在寻找兔子.TC.从类型系统的角度来看,这是一个简写:rabbit.type#TC,其中rabbit.type是表示包的类型,就像它是一个常规对象一样.调用规则4,为我们提供TC和p.type部分.
那么,这一切意味着什么呢?简单地说,包对象中的隐式成员也是隐式作用域的一部分!
在上面的示例中,这为隐式搜索的第二阶段提供了明确的选择.
其他例子可以用同样的方式解释.
综上所述:
>隐式搜索分两个阶段进行.通常的导入和阴影规则决定候选人名单.
>假设您使用的是nested packages,封闭包对象中的隐式成员也可能在范围内.
>如果有多个候选者,则使用静态重载规则来查看是否有胜利者.作为addiotnal的一个决胜局,编译器更喜欢在第一个超类中定义的另一个.
>如果第一阶段失败,则以相同的方式查询隐式范围. (不同之处在于来自不同同伴的隐式成员可能具有相同的名称而不会相互遮蔽.)
>封装包中的包对象中的Implicits也是此隐式范围的一部分.
UPDATE
在Scala 2.9.2中,行为是不同的和错误的.
package rabbit { trait TC object Test extends App { implicit object testInstance1 extends TC { override def toString = "test1" } { implicit object testInstance2 extends TC { override def toString = "test2" } // wrongly considered non-ambiguous in 2.9.2. The sub-class rule // incorrectly considers: // // isProperSubClassOrObject(value <local Test>,object Test) // isProperSubClassOrObject(value <local Test>,{object Test}.linkedClassOfClass) // isProperSubClassOrObject(value <local Test>,<none>) // (value <local Test>) isSubClass <none> // <notype> baseTypeIndex <none> >= 0 // 0 >= 0 // true // true // true // true // // 2.10.x correctly reports the ambiguity,since the fix for // // https://issues.scala-lang.org/browse/SI-5354?focusedCommentId=57914#comment-57914 // https://github.com/scala/scala/commit/6975b4888d // println(implicitly[TC]) } } }