【问题标题】:Implicit categories in type class resolution类型类解析中的隐式类别
【发布时间】:2021-01-07 15:04:30
【问题描述】:

我怀疑,

大多数人都知道Show示例来介绍类型类。

我发现了这篇博文https://scalac.io/typeclasses-in-scala/,当我偶然发现一些我不太理解的东西并希望有人可以帮助澄清它时,我很容易就解决了。

我理解博文中关于隐式类别的所有内容:

从带有语法和对象接口的类型类完整定义

trait Show[A] {
  def show(a: A): String
}

object Show {
  def apply[A](implicit sh: Show[A]): Show[A] = sh

  //needed only if we want to support notation: show(...)
  def show[A: Show](a: A) = Show[A].show(a)

  implicit class ShowOps[A: Show](a: A) {
    def show = Show[A].show(a)
  }

  //type class instances
  implicit val intCanShow: Show[Int] =
    int => s"int $int"

  implicit val stringCanShow: Show[String] =
    str => s"string $str"
}

我们得到以下评论:

我们可能会遇到需要重新定义一些默认类型类实例。使用上面的实现,如果所有默认实例都被导入作用域,我们就无法实现。编译器会在范围内有模糊的隐式,并会报告错误。

我们可能决定移动 show 函数和 ShowOps 隐式类 到另一个对象(比如说操作)以允许此类用户 重新定义默认实例行为(使用类别 1 隐式, 更多关于隐含类别的信息)。经过这样的修改,秀 对象看起来像这样:

object Show {

  def apply[A](implicit sh: Show[A]): Show[A] = sh

  object ops {
    def show[A: Show](a: A) = Show[A].show(a)

    implicit class ShowOps[A: Show](a: A) {
      def show = Show[A].show(a)
    }
  }

  implicit val intCanShow: Show[Int] =
    int => s"int $int"

  implicit val stringCanShow: Show[String] =
    str => s"string $str"

}

用法不变,但现在该类型类的用户只能导入:

import show.Show
import show.Show.ops._

默认隐式实例不作为第 1 类隐式引入(尽管它们可作为第 2 类隐式使用),因此可以在使用此类类型类的地方定义我们自己的隐式实例。

我没有收到最后一条评论?

【问题讨论】:

    标签: scala functional-programming typeclass implicit purely-functional


    【解决方案1】:

    Show[Int]Show[String] 的隐式实例在 Show 伴随对象中定义,因此无论何时使用 Show 类型的值,类型类实例都将可用。但是,它们可以被用户覆盖。这使它们成为第 2 类隐式 - 它们来自 隐式范围

    另一方面,通过直接导入引入范围的隐式是第 1 类隐式。它们来自本地范围,不能被覆盖。因此,直接导入隐式与在现场定义它们相同 - 两者都被视为第 1 类。如果在本地范围内存在多个相同类型的第 1 类隐式值,编译器会报错。

    文章所说的是,将您的隐式实现放在伴随对象中,但将“机器”放在ops 中。这样,您的类型类的用户可以只导入允许他们执行的机器,例如42.show,没有将类型类实例作为类别 1 值引入。

    我们的用户可以这样做:

    import show.Show
    import show.Show.ops._
    
    // available from Show as category 2 implicit:
    println(42.show) // "int 42"
    

    还有:

    import show.Show
    import show.Show.ops._
    
    // overriding category 2 implicit with our own category 1 implicit:
    implicit val myOwnIntCanShow: Show[Int] = int => s"my own $int"
    println(42.show) // prints "my own 42"
    

    但是,如果我们没有 ops 对象,而我们只是将所有内容都放在 Show 对象中,那么每当我们的用户会做 import Show._ 时(他们需要这样做才能做到42.show) 他们将接收我们所有的隐式作为第 1 类值并且无法覆盖它们:

    import show.Show
    
    // Assuming everything is in `Show` (no `ops`)...
    import show.Show._
    
    implicit val myOwnIntCanShow: Show[Int] = int => s"my own $int"
    
    // this line doesn't compile because implicits were brought 
    // into scope as category 1 values (via import Show._)
    println(42.show)
    

    【讨论】:

    • 我看到的第一个区别是 import show.Show import show.Show.ops._ 而你只放了 import show.Show.ops._ 。所以这意味着作者在那里做错了吗?因为这让我感到困惑。使用 Show 对象和 show.ops 对象,您可以获得解决它们的实例和方法,而您的解决方案仅导入解决隐式 show.Show.ops._ 的方法,在这种情况下,是的,首先要看的是您的当前范围。我说对了吗?
    • import show.Show 用于Show 类型(特征)。我省略了它只是因为我已经在同一个文件中。但是,import show.Show._(下划线!)是从 Show 伴随对象导入值的内容,这就是我在上一个示例中所做的。这会阻止类型类的用户定义他们自己的隐式实例。如果我们不导入 Show._,它们将被解析为类别 2 值,因为它们存在于 Show 对象中。这是 Scala 的一个特性——当你需要为某个 A 隐式 F[A] 时,编译器会查看 A 的伴生对象(以及其他地方)。
    • 我会将import Show 添加到我的示例中以避免混淆。
    【解决方案2】:

    本教程的作者调用高优先级隐式(来自本地范围)Category-1 隐式和低优先级隐式(来自隐式范围)Category-2 隐式 .

    Where does Scala look for implicits?

    https://docs.scala-lang.org/tutorials/FAQ/finding-implicits.html

    【讨论】:

    • 非常感谢@Dmytro。一如既往的非常详细和精确。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-18
    • 2012-08-25
    • 1970-01-01
    • 2021-08-22
    • 1970-01-01
    相关资源
    最近更新 更多