6

我正在使用日期为(20120522)的 scala 2.10.0-snapshot 并具有以下 Scala 文件:

这个定义了 typeclass 和一个基本的 typeclass 实例:

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是一个简单的类型类,它定义了一个 aRabbit试图找到的方法。我试图弄清楚它的隐含解析规则。

  • 由于所有四个类型类实例都未注释,scalac 抱怨rabbitHoleInHolePackagerabbitHoleInHole. (为什么?)

  • 如果我注释掉rabbitHoleInHole,scalac 编译并返回“Rabbit 在 Hole 包对象中找到了洞”。(本地范围内的隐含不应该优先吗?)

  • 如果我随后发表评论rabbitHoleInHolePackage,scalac 会抱怨 and 上的rabbitHoleInOuterTest歧义rabbitHoleInInnerTest。(为什么?在 eed3si9n 的文章,下面列出的 url 中,他发现隐式 btw 内部和外部范围可以采取不同的优先级。)

  • 如果我随后注释掉rabbitHoleInInnerTest,scalac 编译并返回“兔子在外部测试对象中找到了洞”。

如您所见,上述行为根本不遵循我所读过的关于隐式解析的规则。我只描述了在注释/取消注释实例时可以做的一小部分组合,其中大多数确实很奇怪——而且我还没有进入导入和子类。

我已经阅读并观看了 suereth 的演示文稿、sobralstackoverflow 回答以及 eed3si9n非常详尽的重访,但我仍然完全感到困惑。

4

1 回答 1

4

让我们从包对象中的隐式和禁用的类型类伴侣开始:

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 查找范围内的任何隐式、查找testInstance1testInstance2. 只有当它们具有相同的名称时,它们才具有更紧密的范围这一事实——通常的阴影规则适用。我们选择了不同的名称,并且没有一个隐含的名称比另一个更具体,因此可以正确报告歧义。

让我们尝试另一个示例,这次我们将在本地范围内与包对象中的隐式进行对比。

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])
    }
  }
}

这里会发生什么?和以前一样,隐式搜索的第一阶段考虑调用站点范围内的所有隐式。在这种情况下,我们有testInstance2packageInstance。这些是模棱两可的,但在报告该错误之前,第二阶段开始,并搜索TC.

但是这里的隐含范围是什么?TC甚至没有伴生对象?我们需要在这里查看 Scala Reference 的 7.2 中的精确定义。

类型 T 的隐式范围由与隐式参数类型相关联的类的所有伴随模块(第 5.4 节)组成。在这里,我们说类 C 与类型 T 相关联,如果它是 T 的某个部分的基类(第 5.1.2 节)。

类型的部分T是:

  • ifT是一个复合类型T1 with ... with Tn, 的各部分的并集T1, ..., Tn, 以及它T本身,
  • ifT是参数化类型S[T1, ..., Tn],和的部分的S并 集T1,...,Tn
  • 如果T是单例类型p.type,则类型的部分p
  • ifT是一个类型 projection S#U,其部分S以及它T本身,
  • 在所有其他情况下,只是T它自己。

我们正在寻找rabbit.TC. 从类型系统的角度来看,这是: 的简写rabbit.type#TC,其中rabbit.type是表示包的类型,就好像它是一个常规对象一样。调用规则 4,为我们提供了部分TCp.type

那么,这一切意味着什么?简单地说,包对象中的隐式成员也是隐式范围的一部分!

在上面的示例中,这为我们在隐式搜索的第二阶段提供了明确的选择。

其他例子可以用同样的方式解释。

总之:

  • 隐式搜索分两个阶段进行。导入和跟踪的通常规则确定候选人列表。
  • 假设您使用的是嵌套包,封闭包对象中的隐式成员也可能在范围内。
  • 如果有多个候选人,则使用静态重载的规则来查看是否有获胜者。一个附加的决胜局,编译器更喜欢隐式而不是在第一个超类中定义的另一个。
  • 如果第一阶段失败,则以几乎相同的方式查询隐式范围。(不同之处在于来自不同同伴的隐式成员可能具有相同的名称而不会相互影响。)
  • 来自封闭包的包对象中的隐式也是此隐式范围的一部分。

更新

在 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])
    }
  }
}
于 2012-05-26T19:16:00.490 回答