【问题标题】:Returning a value from a method which must implement a typeclass从必须实现类型类的方法返回值
【发布时间】:2020-12-29 17:22:45
【问题描述】:

我试图让一个方法返回一个必须实现类型类的值,我认为它更普遍地向我强调了我如何不理解 Scala 的通用参数解析过程。我有这样的情况:

trait IsContainer[A, T] {
  def getOtherContainer[O](implicit tc2: IsUnit[O]): O
}

trait IsUnit[A] { }
implicit val anIntIsUnit = new IsUnit[Int] { }

implicit def aListIsContainer[T] = new IsContainer[List[T], T] {
  def getOtherContainer[Int] = 3
}

这会引发编译错误:Missing implementation for: def getOtherContainer。我对这里应该发生的事情的无知猜测是 Scala 看到我已经传递了泛型参数 O,并认为如果 O 类型的所有实例都一致,则该方法已实现。所以在这种情况下(因为我已经明确告诉它O = Int,它会检查范围内是否存在IsUnit[Int] 的实例,并且方法的输出类型是O 类型。如果这是正确的(我不是说它是!)那为什么这不起作用?

更一般地说,如果我跳过 [O] 泛型参数并让它猜测 - 所以我只是用 getOtherContainer = 3 实现了该方法 - 我是否也应该期望它工作?要推断O 应该是什么,它是否会扫描该行并查看O 是否已在提到的三个位置中的任何一个中具体填写,并从中推断?

谢谢!

【问题讨论】:

标签: scala generics typeclass implicit


【解决方案1】:

正确的是

implicit def aListIsContainer[T] = new IsContainer[List[T], T] {
  override def getOtherContainer[O](implicit tc2: IsUnit[O]): O = ???
}

你的类型类

trait IsContainer[A, T] {
  def getOtherContainer[O](implicit tc2: IsUnit[O]): O
}

意味着如果一个类型为AT的元组有一个类型类的实例,那么你就知道如何处理getOtherContainer 对于任何类型O有一个实例输入类IsUnit

当您尝试在实例定义中删除 (implicit tc2: IsUnit[O])[O] 时,您实际上是在尝试违反类型类的约定。

如果你想在一个实例中专门化O(例如O := Int),那么你应该将类型参数O移动到类型类级别

trait IsContainer[A, T, O] {
  def getOtherContainer(implicit tc2: IsUnit[O]): O
}

abstract class IsContainer[A, T, O](implicit tc2: IsUnit[O]) {
  def getOtherContainer: O
}

trait IsContainer[A, T] {
  type O
  def getOtherContainer(implicit tc2: IsUnit[O]): O
}

然后你可以定义一个实例

implicit def aListIsContainer[T] = new IsContainer[List[T], T, Int] {
  override def getOtherContainer(implicit tc2: IsUnit[Int]): Int = 3
}

implicit def aListIsContainer[T] = new IsContainer[List[T], T, Int] {
  override def getOtherContainer: Int = 3
}

implicit def aListIsContainer[T] = new IsContainer[List[T], T] {
  override type O = Int
  override def getOtherContainer(implicit tc2: IsUnit[O]): O = 3
}

相应地。

【讨论】:

    【解决方案2】:

    主要问题是这个定义:

    def getOtherContainer[Int] = 3
    

    在此定义中,Int 是类型参数,而不是类型 Int。完全一样

    def getOtherContainer[T] = 3
    

    所以你没有“明确告诉它O = Int”,你只是使用Int作为类型参数的名称而不是O。由于这与特征中所需的签名不匹配,因此没有有效的 getOtherContainer 实现,您会收到错误消息。

    【讨论】:

    • 谢谢,这很有趣,我完全没有意识到这一点。那么,是否不可能将类型作为泛型参数显式传递给方法?有没有办法让这个例子工作?
    • 类型不是具体的值,所以它们不能被传递到任何地方。当使用类型参数调用方法时,您可以直接指定类型(如IsUnit[Int])或让编译器推断它。但我不确定你到底想做什么,所以很难进一步评论。
    • 好的,谢谢。我有点不清楚为什么可以在IsUnit[Int] 中指定类型,但不能在getOtherContainer[Int] 中指定类型 - 在这两种情况下,看起来应该是用实际类型参数替换泛型类型参数的问题。如果没有指定类型——例如,如果我只使用def getOtherContainer = 3,它是否会尝试根据返回类型推断O 应该是Int
    • @Chrisper;区别在于调用站点/定义站点。调用站点是指定或省略类型参数的位置(即留给编译器推断)。定义站点是在所定义的方法范围内为类型参数指定名称的位置。它可以是 OTARabbit 但给它起一个像 Int 这样的名字只会让那些试图阅读代码的人感到困惑。
    • @jwvh - 对 - 确实有道理。
    猜你喜欢
    • 2015-05-16
    • 2012-08-13
    • 1970-01-01
    • 2017-04-22
    • 2020-12-27
    • 2020-02-16
    • 1970-01-01
    • 1970-01-01
    • 2015-12-15
    相关资源
    最近更新 更多