【问题标题】:Context Bound on a Generic Class Using Implicits使用隐式绑定在泛型类上的上下文
【发布时间】:2020-11-05 22:55:09
【问题描述】:

我正在学习 Scala 以便将其用于项目。

我想更深入了解的一件事是类型系统,因为它是我以前在其他项目中从未使用过的东西。

假设我设置了以下代码:

// priority implicits
sealed trait Stringifier[T] {
  def stringify(lst: List[T]): String
}

trait Int_Stringifier {
  implicit object IntStringifier extends  Stringifier[Int] {
    def stringify(lst: List[Int]): String = lst.toString()
  }
}

object Double_Stringifier extends Int_Stringifier {
  implicit object DoubleStringifier extends Stringifier[Double] {
    def stringify(lst: List[Double]): String = lst.toString()
  }
}

import Double_Stringifier._

object Example extends App {

  trait Animal[T0] {
    def incrementAge(): Animal[T0]
  }

  case class Food[T0: Stringifier]() {
    def getCalories  = 100
  }

  case class Dog[T0: Stringifier]
  (age: Int = 0, food: Food[T0] = Food()) extends Animal[String] {
    def incrementAge(): Dog[T0] = this.copy(age = age + 1)
  }
}

所以在示例中,存在类型错误:

ambiguous implicit values:
[error]  both object DoubleStringifier in object Double_Stringifier of type Double_Stringifier.DoubleStringifier.type
[error]  and value evidence$2 of type Stringifier[T0]
[error]  match expected type Stringifier[T0]
[error]   (age: Int = 0, food: Food[T0] = Food()) extends Animal[String] 

好吧,很公平。但是如果我删除上下文绑定,这段代码就会编译。 IE。如果我将 '''Dog''' 的代码更改为:

case class Dog[T0]
  (age: Int = 0, food: Food[T0] = Food()) extends Animal[String] {
    def incrementAge(): Dog[T0] = this.copy(age = age + 1)
  }

现在我假设这也不会编译,因为这种类型更通用,更模棱两可,但确实如此。

这里发生了什么?我知道当我将上下文绑定时,编译器不知道它是 double 还是 int。但是为什么还要编译一个更通用的类型呢?当然,如果没有上下文绑定,我可能有一个 Dog[String] 等,这也会混淆编译器。

来自answer:“上下文绑定描述了一个隐式值,而不是视图绑定的隐式转换。它用于声明对于某些类型 A,有一个 B[A] 类型的隐式值可用”

【问题讨论】:

    标签: scala generics types implicit context-bound


    【解决方案1】:

    现在我假设这也不会编译,因为这种类型更通用,更模棱两可,但确实如此。

    歧义在隐含之间。两者都有

    Double_Stringifier.DoubleStringifier
    

    Dog[T0: Stringifier] 的匿名证据(因为class Dog[T0: Stringifier](...)class Dog[T0](...)(implicit ev: Stringifier[T0]) 脱糖)是候选人。

    Int_Stringifier#IntStringifier 无关紧要,因为它的优先级较低)。

    现在您删除了上下文绑定,Food() 中只剩下一个隐式参数候选者,因此没有歧义。我看不出更通用的类型是如何相关的。更通用并不意味着更模糊。要么你在隐含之间有歧义。


    实际上,如果您删除导入但保持上下文绑定,则默认值中看不到匿名证据。所以它会考虑歧义,但不会计算孤独:)

    Scala 2.13.2、2.13.3。

    【讨论】:

    • 感谢@Dmytro,非常感谢您的指导,因为我认为这对新手来说是一个非常具有挑战性的领域。我想我正在以错误的方式思考这个问题。当您说“只有一个候选人”时,您指的是 Double_Stringifier 吗?
    • @finite_diffidence 是的。如果您在定义 case class Dog 的范围内删除导入,则隐式将不在范围内。
    • @finite_diffidence 实际上,如果您删除导入但保持上下文绑定,则默认值中看不到匿名证据。因此,它会产生歧义,但不会单独计算 :) Scala 2.13.2。
    • @finite_diffidence 另见“我们在设计隐式时犯的一些错误 – Martin Odersky”中的“错误 #2”youtu.be/1h8xNBykZqM?t=772(从 12:52 开始)
    【解决方案2】:

    在我看来(如果我错了,我希望@DmytroMitin 能纠正我),理解这一点的关键是为food 参数提供的默认值,这使得class Dog 两者都是定义站点,要求一个隐式在调用站点可用,以及一个调用站点,要求一个隐式必须在编译时范围内。

    代码中前面的import 提供了Food() 调用站点所需的隐式函数,但Dog 构造函数需要从其调用站点放置在ev 中的隐式函数。因此模棱两可。

    【讨论】:

    • 实际上,如果我们删除导入但保持上下文绑定,则默认值中看不到匿名证据。因此,它会产生歧义,但不会单独计算 :) Scala 2.13.2。
    • 对。没有importfood() 调用将无法编译(没有隐式可用),但使用import,则生成的ev 证据算作模棱两可。
    • 谢谢@jwvh,所以如果我按照正确的方式,你的意思是在呼叫现场是这样的吗:case class Dog[T0] (age: Int = 0, food: Food[T0] = Food ())(implicit ev: Stringifier[T0]) extends Animal[String] {
    • @finite_diffidence;上下文绑定语法 ([A:B]) 是语法糖。您的Dog 定义站点对您所拥有的内容进行了简化。调用站点是创建新的Dog 实例的地方。但由于food参数的默认值,Dog定义站点也是Food()调用站点。
    猜你喜欢
    • 2020-10-03
    • 1970-01-01
    • 1970-01-01
    • 2011-05-11
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    • 2019-05-31
    相关资源
    最近更新 更多