Scala 多态返回类型

8
我有一个抽象的Scala类Base,它有子类Derived1Derived2Base定义了一个函数f(),它返回与实现类相同类型的对象。因此,Derived1.f()返回Derived1Derived2.f()返回Derived2。我该如何在Scala中编写这个代码?
以下是我目前想到的解决方案。
package com.github.wpm.cancan

abstract class Base {
  def f[C <: Base]: C
}

case class Derived1(x: Int) extends Base {
  def f[Derived1] = Derived1(x + 1)
}

case class Derived2(x: Int) extends Base {
  def f[Derived2] = Derived2(x + 2)
}

这会产生以下编译错误:
type mismatch;
[error]  found   : com.github.wpm.cancan.Derived1
[error]  required: Derived1
[error]   def f[Derived1] = Derived1(x + 1)

type mismatch;
[error]  found   : com.github.wpm.cancan.Derived2
[error]  required: Derived2
[error]   def f[Derived2] = Derived2(x + 2)

这个错误信息让我感到困惑,因为在这个情况下我认为com.github.wpm.cancan.Derived1应该与Derived1相同。

4
def f[Derived1]中,Derived1不是指向case class Derived1的引用,而是作为def f ...的类型参数! - Randall Schulz
我认为你可以在方法上使用“override”关键字来实现你想要的效果(但必须去掉类型参数)。 - didierc
2个回答

15
Randall Schulz指出了你当前代码无法正常工作的原因之一。但是,通过使用F-bounded polymorphism,你仍然可以达到想要的效果。
trait Base[C <: Base[C]] { def f: C }

case class Derived1(x: Int) extends Base[Derived1] {
  def f: Derived1 = Derived1(x + 1)
}

case class Derived2(x: Int) extends Base[Derived2] {
  // Note that you don't have to provide the return type here.
  def f = Derived2(x + 2)
}

基础特征的类型参数允许您在那里谈论实现类,例如在f的返回类型中。请注意保留HTML标签。

这里是另一个使用该语言特性的示例:https://dev59.com/YmLVa4cB1Zd3GeqPz89c - didierc
5
值得指出的是,这种 F-bounded 多态性的特殊实例相对而言比较广为人知,被称作“Curiously Recursive Template/Type Pattern”,用于定义一个基础类型中的函数,其返回值在各个子类型中是多态的。例如,在Java中,枚举类型的定义是class Enum<E extends Enum<E>>。 - Impredicative
2
@Impredicative:我一直听说它被称为奇妙的“递归模板模式”,这更加元。 - Travis Brown

8

关于(非常好的)Travis Brown的回答,我想补充一点:在 trait Base[C <: Base[C]] 中的 C 并不是让你引用实现类的;只是遵循写法约定,即 subclass extends Base[subclass] 才可以这样做。我不知道有什么方法可以引用这种类型。为了澄清我的意思,这段代码可以编译通过。

trait Base[C <: Base[C]] { def f: C }

case class Derived1(x: Int) extends Base[Derived1] {
  def f: Derived1 = Derived1(x + 1)
}
// a Derived2 where f returns Derived1!!
case class Derived2(x: Int) extends Base[Derived1] {
  def f = Derived1(x + 2)
}

现在,如果您将所有的 Base 实现都作为 case classes,您可以通过 self-type bound 来正确实现:
trait Base[C <: Base[C]] { self: C => 
  def f: C 
}

case class Derived1(x: Int) extends Base[Derived1] {
  def f: Derived1 = Derived1(x + 1)
}
// a Derived2 where f returns Derived1!!
// this won't compile now
case class Derived2(x: Int) extends Base[Derived1] {
  def f = Derived1(x + 2)
}

我不明白 - 为什么后一种解决方案只适用于 case 类? - Robin Green
1
如果您的“Base”层次结构只有一个级别(如果它们都是案例类,则为真),那么它将起作用。 - Eduardo Pareja Tobes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接