Scala:如何编写将返回对象的方法写入接收器的实现类型

enh*_*ull 5 scala

我知道在Scala中不推荐使用case类继承,但为了简单起见,我在下面的例子中使用了它:

scala> case class Foo(val f: String) { def foo(g: String): Foo = { this.copy(f=g) }}
defined class Foo

scala> case class Bar(override val f: String) extends Foo(f)
warning: there were 1 deprecation warnings; re-run with -deprecation for details
defined class Bar

scala> Bar("F")
res0: Bar = Foo(F)

scala> res0.foo("G")
res1: Foo = Foo(G)
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.但是,我真正想要的是能够foo()在Foo中编写一个方法,当在类型为Bar的对象上调用时返回Bar类型的对象,而不必重新实现类Bar中的方法.有没有办法在Scala中执行此操作?

Eug*_*ota 5

建设者方法

是的,可以做到.一个很好的例子是集合库.

scala> List(1, 2, 3) take 2
res1: List[Int] = List(1, 2)

scala> Array(1, 2, 3) take 2
res2: Array[Int] = Array(1, 2)
Run Code Online (Sandbox Code Playgroud)

请参阅Scala集合的体系结构以了解它是如何完成的.

编辑:它使用两种方法来重用实现.第一种是使用常见的特征和构建器,另一种是使用类型类.

scala> :paste
// Entering paste mode (ctrl-D to finish)

trait Builder[A] {
  def apply(f: String): A
}
trait FooLike[A] {
  def builder: Builder[A]
  def f: String
  def genericCopy(f: String): A = builder(f)
  def map(fun: String => String): A = builder(fun(f))
}
case class Foo(f: String) extends FooLike[Foo] {
  def builder = new Builder[Foo] {
    def apply(f: String): Foo = Foo(f)
  }
}
case class Bar(f: String) extends FooLike[Bar] {
  def builder = new Builder[Bar] {
    def apply(f: String): Bar = Bar(f)
  }
}

scala> Foo("foo").genericCopy("something")
res0: Foo = Foo(something)

scala> Bar("bar").genericCopy("something")
res1: Bar = Bar(something)

scala> Foo("foo") map { _ + "!" }
res2: Foo = Foo(foo!)
Run Code Online (Sandbox Code Playgroud)

这样做的整点,那么你可以做一些事情的共同特点有趣,就像实现共同mapFooLike.用琐碎的代码很难看到它带来的好处.

类型方法

使用类型类的好处是,您可以添加功能Foo,Bar甚至在您无法更改功能时(例如String).

scala> :paste
// Entering paste mode (ctrl-D to finish)

case class Foo(f: String)
case class Bar(f: String)
trait CanCopy[A] {
  def apply(self: A, f: String): A
  def f(self: A): String
}
object CanCopy {
  implicit val fooCanCopy = new CanCopy[Foo] {
    def apply(v: Foo, f: String): Foo = v.copy(f = f)
    def f(v: Foo) = v.f
  }
  implicit val barCanCopy = new CanCopy[Bar] {
    def apply(v: Bar, f: String): Bar = v.copy(f = f)
    def f(v: Bar) = v.f
  }
  implicit val stringCanCopy = new CanCopy[String] {
    def apply(v: String, f: String): String = f
    def f(v: String) = v
  }

  def copy[A : CanCopy](v: A, f: String) = {
    val can = implicitly[CanCopy[A]]
    can(v, f)
  }

  def f[A : CanCopy](v: A) = implicitly[CanCopy[A]].f(v)
}

scala> CanCopy.copy(Foo("foo"), "something")
res1: Foo = Foo(something)

scala> CanCopy.f(Foo("foo"))
res2: String = foo

scala> CanCopy.copy(Bar("bar"), "something")
res3: Bar = Bar(something)

scala> CanCopy.copy("string", "something")
res4: java.lang.String = something
Run Code Online (Sandbox Code Playgroud)


Deb*_*ski 3

注意:这不会创建新对象,而是重新使用该this对象。对于一般用途,请参阅范例\xe2\x80\x99s 答案

\n\n

由于某种原因,它不能与case class\xe2\x80\x99scopy方法一起使用。(但不可否认,由于case class无论如何都不应该进行继承,因此不会出现该问题。)。但对于任何其他方法,您可以使用this.type.

\n\n
case class Foo(val f: String) { def foo(g: String): this.type = { this }}\n\ncase class Bar(override val f: String) extends Foo(f)\n\nBar("F").foo("G")\nres: Bar = Foo(F)\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

如果您需要方法参数和方法主体中的自类型差异(而不是仅返回类型差异),则需要更进一步并定义

\n\n
trait HasFoo[T <: HasFoo[T]] { this: T =>\n  def foo(g:String): T\n  def bar(g: T): T // here may follow an implementation\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这将允许您向trait. (参见:2D 和 3D 向量的正确类层次结构

\n