模块化Scala设计:如何避免构造函数"推出"样板?

Per*_*ing 12 scala boilerplate

也许具有良好风格和优雅感的Scala专家可以帮助我找到一种更好的方法来构造以下代码,这些代码具有构造函数"推出"问题.

我们从一个简单的基类开始:

class Foo(val i: Int, val d: Double, val s: String) {

  def add(f: Foo) = new Foo(i + f.i, d + f.d, s + f.s)
  override def toString = "Foo(%d,%f,%s)".format(i,d,s)

}
Run Code Online (Sandbox Code Playgroud)

为了在复杂的应用程序中进行类型检查,我需要一个没有任何附加状态的子类:

class Bar(i: Int, d: Double, s: String) extends Foo(i,d,s) {

  override def toString = "Bar(%d,%f,%s)".format(i,d,s)

}
Run Code Online (Sandbox Code Playgroud)

就目前而言,当我添加两个Bars时,我只回到Foo:

val x = new Bar(1,2.3,"x")
val y = new Bar(4,5.6,"y")
val xy = x.add(y)
Run Code Online (Sandbox Code Playgroud)

在REPL中有以下响应:

x  : Bar = Bar(1,2.300000,x)
y  : Bar = Bar(4,5.600000,y)
xy : Foo = Foo(5,7.900000,xy)
Run Code Online (Sandbox Code Playgroud)

如何以优雅的方式将两个Bars添加到一起形成另一个Bar(而不是Foo),而无需复制和粘贴Foo的add方法,如下所示?

class Bar(i: Int, d: Double, s: String) extends Foo(i,d,s) {

  // ugly copy-and-paste from Foo:
  def add(b: Bar) = new Bar(i + b.i, d + b.d, s + b.s)
  override def toString = "Bar(%d,%f,%s)".format(i,d,s)

}
Run Code Online (Sandbox Code Playgroud)

我有很多这样的条(基本上都是Foo的副本,但对于类型检查非常重要),一个免切割和粘贴的解决方案将带来红利.

谢谢!

par*_*tic 12

我试图尽可能地避免继承.所以这是另一种方法.

Bar具有完全相同的构造函数,Foo并且都是无状态的.如果您想拥有多个子类型,只是为了传达任何其他信息,您可以使用通用参数作为"标签".例如:

trait Kind
trait Bar extends Kind

class Foo[T<:Kind](val i: Int, val d: Double, val s: String) {
   def add(f: Foo[T]) = new Foo[T](i + f.i, d + f.d, s + f.s)
   override def toString = "Foo(%d,%f,%s)".format(i,d,s)
}


scala> val b1 = new Foo[Bar](2,3.0,"hello")
b1: Foo[Bar] = Foo(2,3.000000,hello)

scala> val b2 = new Foo[Bar](3,1.0," world")
b2: Foo[Bar] = Foo(3,1.000000, world)

scala> b1 add b2
res1: Foo[Bar] = Foo(5,4.000000,hello world)
Run Code Online (Sandbox Code Playgroud)

现在add是类型安全的.然后,您可以使用类型类toString来显示Kind.


Phi*_*ppe 5

扩展@ paradigmatic的答案,如果您希望能够支持特定于每个Bar(例如,不同toString)的操作,您可以更进一步并制作Kind类型类.

trait Kind[T] { def name : String }
trait Bar
implicit object BarHasKind extends Kind[Bar] { val name = "Bar" }

class Foo[T : Kind](val i : Int, val d : Double, val s : String) {
  def add(f : Foo[T]) = new Foo[T](i + f.i, d + f.d, s + f.s)
  override def toString = implicitly[Kind[T]].name + "(%d,%f,%s)".format(i,d,s)
}

scala> val b1 = new Foo[Bar](2, 3.0, "hello")
b1: Foo[Bar] = Bar(2,3.000000,hello)

trait Biz
implicit object BizHasKind extends Kind[Biz] { val name = "Biz" }

scala> val b2 = new Foo[Biz](1, 1.0, "One")
Run Code Online (Sandbox Code Playgroud)

它和以前一样安全:

scala> b1 add b2
<console>:16: error: type mismatch;
  found   : Foo[Biz]
  required: Foo[Bar]

scala> b2 add b2
resN: Foo[Biz] = Biz(2,2.000000,OneOne)
Run Code Online (Sandbox Code Playgroud)

对于您希望依赖于标记的任何属性,将它们抽象地声明Kind并在隐式对象中提供实现.