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

时间:2011-07-11 08:36:09

标签: 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)

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

4 个答案:

答案 0 :(得分: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)

请参阅The Architecture of Scala Collections了解其完成情况。

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

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!)

这样做的全部意义在于,您可以在共同特征上做一些有趣的事情,例如在map中实施常见的FooLike。用琐碎的代码很难看到好处。

类型类方法

使用类型类的好处是,即使您无法更改功能,也可以向FooBar添加功能(例如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

答案 1 :(得分:4)

复制方法由编译器实现,它似乎不属于常见特征。最简单的方法是定义一个特征:

trait HasFoo[T] {
  def foo(g:String): T
}

case class Foo( f: String ) extends HasFoo[Foo] {
  def foo( g: String ) = copy(f=g)
}

case class Bar( f: String ) extends HasFoo[Bar] {
  def foo( g: String ) = copy(f=g)
}

scala> Bar("a").foo("b")
res7: Bar = Bar(b)

scala> Foo("a").foo("b")
res8: Foo = Foo(b)

另一种选择是使用类型类来提供适当的构建器。但它不会保存打字字符的数量。

答案 2 :(得分:3)

注意:这不会创建新对象,但会重新使用this对象。一般用途,请参阅paradigmatic’s answer

出于某种原因,它不能与case class的{​​{1}}方法一起使用。 (但诚然,由于copy继承不应该进行,因此问题不会发生。)但对于任何其他方法,您都可以使用case class

this.type

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

case class Foo(val f: String) { def foo(g: String): this.type = { this }}

case class Bar(override val f: String) extends Foo(f)

Bar("F").foo("G")
res: Bar = Foo(F)

这将允许您向trait HasFoo[T <: HasFoo[T]] { this: T => def foo(g:String): T def bar(g: T): T // here may follow an implementation } 添加适当的方法体。 (见:proper class hierarchy for 2D and 3D vectors

答案 3 :(得分:-3)

此解决方案不需要单独的特征。

class Bar

class Foo {
  def returnMyType[A](x:A) :A = { println(x); x }
}

val f = new Foo
val b = new Bar

val bReturned = f.returnMyType(b)

println(bReturned.getClass.getName)