scala - scala 2.10.x 中的隐式解析。这是怎么回事?

标签 scala

我使用的是 scala 2.10.0-snapshot (20120522),并且有以下 Scala 文件:

这定义了类型类和基本类型类实例:

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 提示 rabbitHoleInHolePackagerabbitHoleInHole 上存在歧义。 (为什么?)

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

  • 如果我随后注释掉 rabbitHoleInHolePackage,scalac 会提示 rabbitHoleInOuterTestrabbitHoleInInnerTest 上的歧义。 (为什么?在 eed3si9n 的文章中,下面列出了 url,他发现隐式 btw 内部和外部范围可以采取不同的优先级。)

  • 如果我随后注释掉 rabbitHoleInInnerTest,scalac 会编译并返回“Rabbit 发现外部测试对象中的洞”。

正如您所看到的,上述行为根本不遵循我在隐式解析中阅读的规则。我只描述了在注释/取消注释实例时可以执行的一小部分组合,其中大多数确实非常奇怪 - 而且我还没有讨论导入和子类。

我已阅读并观看 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 查找任何范围内的隐式,找到 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 引用文献 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 type T1 with ... with Tn, the union of the parts of T1, ..., Tn, as well as T itself,
  • if T is a parameterized type S[T1, ..., Tn], the union of the parts of S and T1,...,Tn,
  • if T is a singleton type p.type, the parts of the type of p,
  • if T is a type projection S#U, the parts of S as well as T itself,
  • in all other cases, just T itself.

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

那么,这一切意味着什么?简而言之,包对象中的隐式成员也是隐式作用域的一部分!

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

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

总结:

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

更新

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

关于scala - scala 2.10.x 中的隐式解析。这是怎么回事?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10763511/

相关文章:

scala - 写入同一文件的多个 Actor + 旋转

"+:"和 "::"之间的 Scala 区别

scala - 如何使用 shapeless 在 Scala 列表中拆分

mysql - 光滑计数(.length/.size)无法按预期工作

scala - 为 A 和 B 类型创建 given 的最简单方法是什么?

scala - Compose 和 andThen 方法

scala - 如何使用两种参数类型在 Scala 中实现泛型函数?

postgresql - 简单的 Slick 示例 Scala

java - 在 Scala 中有效地从字符串中提取日期

scala - 是否可以使用 scala-sbt 在开发模式下发布本地?