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

发布于 2024-11-19 09:56:32 字数 569 浏览 6 评论 0原文

我知道 Scala 中不推荐使用案例类继承,但为了简单起见,我在以下示例中使用了它:

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 类型的对象,而不必在 Foo 中重新实现该方法类酒吧。有没有办法在 Scala 中做到这一点?

I'm aware that case class inheritance is deprecated in Scala, but for the sake of simplicity, I've used it in the following example:

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)

So far, so good. What I really want, though, is to be able to write a method foo() in Foo that returns an object of type Bar when called on an object of type Bar, without having to reimplement the method in class Bar. Is there a way to do this in Scala?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

听不够的曲调 2024-11-26 09:56:32

构建器方法

是的,可以做到。一个很好的例子就是馆藏图书馆。

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)

请参阅 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!)

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

类型类方法

使用类型类的好处是,您可以向 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

builder approach

Yes, it can be done. A good example of that is the collections library.

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)

See The Architecture of Scala Collections to see how it was done.

Edit:
It uses two approaches to reuse implementations. The first is by using common traits and builders, and the other is using type classes.

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

The whole point of doing this, is so you can do something interesting at the common trait, like implementing common map in FooLike. It's hard to see the benefits with trivial code.

type class approach

The benefit of using a type class is that you can add features to Foo and Bar even when you can't change them (like 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
瞎闹 2024-11-26 09:56:32

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

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)

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

The copy method is implemented by the compiler and it does not seem to belong a common trait. The easiest way to do it is to define a trait:

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)

Another option is to use type classes to provide an appropriate builder. But it wont save the number of typed characters.

暮色兮凉城 2024-11-26 09:56:32

注意:这不会创建新对象,而是重新使用 this 对象。对于一般用途,请参阅 范式的回答

由于某种原因,它不能与 case 类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
}

这将允许您将适当的方法主体添加到 特征。 (请参阅:2D 和 3D 向量的正确类层次结构

Note: This does not create a new object but re-uses the this object. For general use, see paradigmatic’s answer.

For some reason, it does not work together with the case class’s copy method. (But admittedly, since case class inheritance should not be done anyway, the problem does not occur.). But for any other method, you do it with 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)

If you need the self-type variance in method arguments and method bodys (as opposed to return-type-only variance), you will need to go one step further and define

trait HasFoo[T <: HasFoo[T]] { this: T =>
  def foo(g:String): T
  def bar(g: T): T // here may follow an implementation
}

This will allow you to add proper method bodies to the trait. (See: proper class hierarchy for 2D and 3D vectors)

遗忘曾经 2024-11-26 09:56:32

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

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)

This solution doesn't require a separate trait.

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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文