【问题标题】:Specify concrete type for methods in Scala trait为 Scala trait 中的方法指定具体类型
【发布时间】:2021-06-25 14:14:27
【问题描述】:

我想在 Scala trait 中定义一个方法,其中方法的参数和返回类型对应于扩展该 trait 的同一个具体类。我尝试过类似以下的方法:

trait A {
  def foo(obj: this.type): this.type
}

final case class B(val bar: Int) extends A {
  override def foo(obj: B): B = {
    B(obj.bar + this.bar)
  }
}

object Main {
  def main(args: Array[String]) = {
    val b1 = new B(0)
    val b2 = new B(0)
    val b3: B = b1.foo(b2)
  }
}

但是,尝试编译此代码会出现以下错误:

Test.scala:5: error: class B needs to be abstract. Missing implementation for:
  def foo(obj: B.this.type): B.this.type // inherited from trait A
case class B(val bar: Int) extends A {
           ^
Test.scala:6: error: method foo overrides nothing.
Note: the super classes of class B contain the following, non final members named foo:
def foo: ((obj: _1.type): _1.type) forSome { val _1: B }
  override def foo(obj: B): B = {
               ^
2 errors

显然我对这里的 Scala 类型系统有一些误解。 fooB 类中的签名是我想要的,但我不知道如何正确定义A 中的方法(或者如果这甚至可能)。 this question 似乎在问一些非常相似的问题,但我没有立即看到答案如何适用于我的情况。

【问题讨论】:

  • this.type 的住户永远只有一个,那就是this。您的“覆盖”说“我将采用 B 并返回一些,也许是其他的 B 实例”,这不够具体。
  • @OlegPyzhcov 我确实想返回一些,也许是 B 的其他实例。我意识到我的示例可能不清楚,因为我不知道 foo 方法可能是什么样子。我更新了示例。

标签: scala inheritance typing


【解决方案1】:

类型注解this.type表示你只能返回this。因此,在这种情况下,您可能不会返回 B 的另一个实例,方法参数也是如此。

如果这只是返回类型,一个解决方案是要求foo返回A类型的东西,B中的覆盖方法可以将返回类型专门化为返回B

但是,由于您还有一个想要成为子类型类型的参数,您可以使用Self Recursive Type。下面的示例编译并应该做你想做的。

  trait A[S <: A[S]] {
    def foo(obj: S): S
  }

  case class B(val bar: Int) extends A[B] {
    override def foo(obj: B): B = {
      B(obj.bar + 1)
    }
  }

【讨论】:

  • Afoo 的返回类型更改为A 而不是this.type 会产生类似的错误。
  • 更新了我的答案,自递归类型似乎是这里的解决方案
  • 这似乎确实解决了这个特定问题。虽然显然意味着向A 添加一个类型参数。这使得在其他地方使用 A 变得复杂,所以我希望尽可能避免这种情况。
  • 请注意,这不仅丑陋,而且实际上也不太正常。考虑 class C extends B ... 它可以编译,但 C.foo 方法被定义为 B =&gt; B,而不是预期的 C =&gt; C
  • @MichaelMior 的重点是,可以绕过此定义并创建A 的实例,其foo 方法不返回其类的实例。
【解决方案2】:

考虑类型类解决方案

case class B(bar: Int)

// type class
trait Fooable[A] {
  def foo(x: A, y: A): A
}

// proof that B satisfies Fooable constraints
implicit val fooableB: Fooable[B] = new Fooable[B] {
  override def foo(x: B, y: B): B = B(x.bar + y.bar)
}

// a bit of syntax sugar to enable x foo y
implicit class FooableOps[A](x: A) {
  def foo(y: A)(implicit ev: Fooable[A]) = ev.foo(x,y)
}

val b1 = B(1)
val b2 = B(41)
b1.foo(b2)
// B(42)

哪个 Scala 3 simplifies

case class B(bar: Int)

// type class
trait Fooable[A] {
  extension (x: A) def foo (y: A): A
}

// proof that B satisfies Fooable constraints + syntactic sugar
given Fooable[B] with
   extension (x: B) def foo (y: B): B = B(x.bar + y.bar) 


val b1 = B(1)
val b2 = B(41)
b1.foo(b2)
// B(42)

请参阅 Scala 常见问题解答:How can a method in a superclass return a value of the “current” type?

【讨论】:

  • Scala 2 示例缺少一个对象来包装隐式,但似乎可以正常工作。但是,这似乎不允许与我原来的示例中的 A 的另一个子类等效。
  • @MichaelMior 这是一种不同的技术,而不是子类化,我们提供了另一个“证明”新类满足约束条件。假设我们希望类 Zar 获得 foo 能力,然后我们显示 Fooable[Zar] 是给定的。
  • 假设我为B 和另一个类C 实现Fooable。一个有效的类型是什么样的,它可以同时保存BC 实例并提供对foo 方法的访问?
  • @MichaelMior 给定Fooable[B]Fooable[C],然后v in def f[A: Fooable](v: A) = ??? 获得foof(new B)f(new C) 的能力。
猜你喜欢
  • 1970-01-01
  • 2011-10-26
  • 2014-10-09
  • 2013-07-27
  • 1970-01-01
  • 2012-09-08
  • 1970-01-01
  • 2021-09-13
  • 1970-01-01
相关资源
最近更新 更多