【问题标题】:code reducing by using traits and the with-keyword通过使用特征和 with-keyword 减少代码
【发布时间】:2011-05-02 19:01:51
【问题描述】:

我有一些具有相同超类型的类。因此,所有这些类都必须覆盖相同的方法。现在我可以调用一个方法并将它提交为一个普通超类型的对象。但是对每个提交的类型做出反应并不总是有用的,因此会引发异常。首先,我尝试像这样解决这种行为:

def operation(s: SuperType) = s match {
  case t: SubType1 => ...
  case t: SubType2 => ...
  case _ => ...
}

由于有很多子类型,这将导致大量代码(在每个方法和每个类中),我试图用traits 解决这个问题。每个特征应该只测试一种类型,然后将对象转发到堆栈上更高的方法。下面的代码描述了我的想象。但这不起作用,因为编译器无法分解类型。另一个问题是我必须在每个行为类中声明类的每个属性。

object TraitWithTest {
  def main(args: Array[String]) {
    val e1 = Even(2, 4)
    val e2 = Even(1, 3)
    val o1 = Odd(1.25, 3.75)
    val o2 = Odd(7.25, 9.25)
    val a1 = All(5.5)
    val a2 = All(3.5)

    println("e1 + e2: " + (e1 + e2))
    println("o1 + o2: " + (o1 + o2))
    try { println("e1 + o2: " + (e1 + o2)) } catch { case e => println(e) }
    println("o1 + e2: " + (o1 + e2))
    println("a1 + e1: " + (a1 + e2))
  }
}

abstract class Num {
  def +(n: Num): Num
}

trait OddBehaviour extends Num {
  val e1, e2: Int // here I don't want to declare all attributes
  val a1: Double
  abstract override def +(n: Num) = n match {
    case o: Odd => throw new UnsupportedOperationException("Even#+(Odd)")
    case _ => super.+(n)
  }
}

trait EvenBehaviour extends Num {
  val o1, o2: Double
  val a1: Double
  abstract override def +(n: Num) = n match {
    case e: Even => Odd(o1 + e.e1, o2 + e.e2)
    case _ => super.+(n)
  }
}

trait AllBehaviour extends Num {
  val o1, o2: Double
  val e1, e2: Int
  abstract override def +(n: Num) = n match {
    case a: All => Odd(o1 + a.a1, o2 + a.a1)
    case _ => super.+(n)
  }
}

object Even {
  def apply(e1: Int, e2: Int) = new Even(e1, e2) with OddBehaviour with AllBehaviour
}

abstract case class Even(e1: Int, e2: Int) extends Num {
  override def +(n: Num) = n match {
    case c: Even => Even(e1 + c.e1, e2 + c.e2)
    case _ => throw new IllegalArgumentException
  }
}

object Odd {
  def apply(o1: Double, o2: Double) = new Odd(o1, o2) with EvenBehaviour with AllBehaviour
}

abstract case class Odd(o1: Double, o2: Double) extends Num {
  override def +(n: Num) = n match {
    case o: Odd => Odd(o1 + o.o1, o2 + o.o2)
    case _ => throw new IllegalArgumentException
  }
}

object All {
  def apply(a1: Double) = new All(a1) with EvenBehaviour with OddBehaviour
}

abstract case class All(a1: Double) extends Num {
  override def +(n: Num) = n match {
    case a: All => All(a1 + a.a1)
    case _ => throw new IllegalArgumentException
  }
}

有人可以告诉我是否可以通过使用特征来减少代码行数?还是我目前使用的最好的全匹配解决方案?

编辑:

在您的帮助下,我找到了一个半途而废的解决方案。我的主要问题是我试图通过使用 Scala 功能来减少代码行数。所以我忽略了最简单的方法:外包代码!我只需要创建一个检查对象组合的新对象。对象本身只处理它们自己的类型。

这是代码:

final object TraitWithTest {
  def main(args: Array[String]) {
    import traitwith.operations._
    val e1 = Even(2, 4)
    val e2 = Even(1, 3)
    val o1 = Odd(1.25, 3.75)
    val o2 = Odd(7.25, 9.25)
    val a1 = All(5.5)
    val a2 = All(3.5)

    val n1 = NumHolder(o1)
    val n2 = NumHolder(a1)

    println("e1 + e2: " + add(e1, e2))
    println("o1 + o2: " + add(o1, o2))
    try { println("e1 + o2: " + add(e1, o2)) } catch { case e => println(e) }
    println("o1 + e2: " + add(o1, e2))
    try { println("a1 + e2: " + add(a1, e2)) } catch { case e => println(e) }
    println("n1 + n2: " + add(n1, n2))
  }
}

final object operations {
  def add(a: Num, b: Num) = a -> b match {
    case (a1: Odd, b1: Odd) => a1 + b1
    case (a1: Odd, b1: Even) => Odd(a1.x + b1.x, a1.y + b1.y)
    case (a1: Odd, b1: All) => Odd(a1.x + b1.x, a1.y + b1.x)
    case (a1: Even, b1: Even) => a1 + b1
    case (a1: All, b1: All) => a1 + b1
    case _ => error("can't add " + b + " to " + a)
  }
}

abstract class Num {
  type A <: Num
  def +(a: A): A
}

final case class Odd(x: Double, y: Double) extends Num {
  override type A = Odd
  override def +(a: Odd) = Odd(x + a.x, y + a.y)
}

final case class Even(x: Int, y: Int) extends Num {
  override type A = Even
  override def +(a: Even) = Even(x + a.x, y + a.y)
}

final case class All(x: Double) extends Num {
  override type A = All
  override def +(a: All) = All(x + a.x)
}

final case class NumHolder(x: Num) extends Num {
  override type A = NumHolder
  override def +(a: NumHolder) = NumHolder(x + a.x)
}

我稍微扩展了代码并插入了对象NumHolder。现在,只有一个小缺陷:在 NumHolder 中,如果在 add-method 中出现编译错误,我无法提交超类型。我尝试使用泛型而不是类型关键字,但这并不方便,因为我总是将类型设置为 Num(也在对象 operations 中)。

我该如何解决这个小小的编译错误?

【问题讨论】:

  • 你能解释一下你首先要完成什么吗?
  • 这些类包含不同的数学值。例如 Rational、Complex、Vector、Matrix... 当一个值被添加到另一个值时,有不同的“方法”可以做到这一点,所以我必须检查值的类型,然后使用正确的操作。

标签: scala traits reduce


【解决方案1】:

您的问题是您尝试在非面向对象的设计中使用面向对象的功能,例如类和继承。

OOP 的全部要点是你不内省什么是类。相反,使用多态性来实现结果。我特别喜欢 this paper 说明 OO 应该如何工作,但在这方面并不缺乏资源。

编辑

例如,提供的代码大致翻译成以下内容,减去不起作用的东西(因为它们,提供的代码不能精确编译)。

abstract class Num {
  def +(n: Num): Num
  def plus(n1: Int, n2: Int): Num
  def plus(n1: Double, n2: Double): Num
  def plus(n: Double): Num
}

case class Even(e1: Int, e2: Int) extends Num {
  override def +(n: Num) = n.plus(e1, e2)
  override def plus(n1: Int, n2: Int) = Even(e1 + n1, e2 + n2)
  override def plus(n1: Double, n2: Double) = Odd(n1 + e1, n2 + e2)
  // the code provided references o1 and o2, which are not defined anywhere for Even
  // so I'm providing an alternate version
  override def plus(n: Double) = Odd(n + e1, n + e2)
}

case class Odd(o1: Double, o2: Double) extends Num {
  override def +(n: Num) = n.plus(o1, o2)
  override def plus(n1: Int, n2: Int) = throw new UnsupportedOperationException("Even#+(Odd)")
  override def plus(n1: Double, n2: Double) = Odd(o1 + n1, o2 + n2)
  override def plus(n: Double) = throw new UnsupportedOperationException("Even#+(Odd)")
}

case class All(a1: Double) extends Num {
  override def +(n: Num) = n.plus(a1)
  // the code provided references o1 and o2, which are not defined anywhere for All
  // so I'm providing an alternate version
  override def plus(n1: Int, n2: Int) = Odd(a1 + n1, a1 + n2)
  override def plus(n1: Double, n2: Double) = Odd(n1 + a1, n2 + a1)
  override def plus(n: Double) = All(a1 + n)
}

在我看来,它可以通过访问者模式进一步改进,这是有道理的,因为它针对的问题与类型匹配通常会解决的问题相同。

【讨论】:

  • 我不明白多态在这里有什么帮助。例如,必须可以将对象 All、Even 和 Odd 添加到 Odd。每个类都必须对提交的参数做出反应。我可以使用重载方法,而不是使用匹配关键字。但这没有什么区别。有了多态性并且没有重载,一个类只能对一种类型做出反应,而不能对多种类型做出反应。
【解决方案2】:

错误引用某个广告:有一个类型类...

Scala 已经通过Numeric 支持“数字”上的临时多态性,这可能是您真正想要的:http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/scala/math/Numeric.html

但是,如果这个 Even/Odd/All 方案是您实际在做的,而不仅仅是一个人为的示例,那么您总是可以推出自己的类型类!


我们就叫它Addable

case class Even(x:Int, y:Int)
case class Odd(x:Double, y:Double)
case class All(x:Double)

abstract class Addable[A, B] {
  def add(a: A, b: B): A
}

implicit object EvenCanAddEven extends Addable[Even, Even] {
  def add(a:Even, b:Even) = Even(a.x+b.x, a.y+b.y)
}

implicit object OddCanAddOdd extends Addable[Odd, Odd] {
  def add(a:Odd, b:Odd) = Odd(a.x+b.x, a.y+b.y)
}

implicit object OddCanAddEven extends Addable[Odd, Even] {
  def add(a:Odd, b:Even) = Odd(a.x+b.x, a.y+b.y)
}

implicit object AllCanAddAll extends Addable[All, All] {
  def add(a:All, b:All) = All(a.x+b.x)
}

def add[A,B](a:A, b:B)(implicit tc: Addable[A,B]) =
  tc.add(a, b)


val e1 = Even(2, 4)
val e2 = Even(1, 3)
val o1 = Odd(1.25, 3.75)
val o2 = Odd(7.25, 9.25)
val a1 = All(5.5)
val a2 = All(3.5)

println("e1 + e2: " + add(e1, e2))
println("o1 + o2: " + add(o1, o2))
println("e1 + o2: " + add(e1, o2)) //compiler should fail this line
println("o1 + e2: " + add(o1, e2))
println("a1 + e1: " + add(a1, e2))

免责声明: 我还没有实际测试过代码,这台机器(还)没有安装 Scala

【讨论】:

  • 非常感谢凯文!您的代码工作正常。它只有一个缺陷:它不能在运行时工作。我的程序应该解析字符串,所以我需要在运行时进行类型检查。我使用多态性。在数值类之外的代码中,我只使用它们的超类型。我只有在这个类里面有问题。
  • 您可以随时在 simplescala.com 或 ideone.com 上测试您的代码。
【解决方案3】:

另一种解决方案,用于在运行时才知道类型的情况:

sealed trait Num
case class Even(x:Int, y:Int) extends Num
case class Odd(x:Double, y:Double) extends Num
case class All(x:Double) extends Num

object operations {
  def add(a: Num, b: Num) : Num = (a,b) match {
    case (a1:Even, b1:Even) => Even(a1.x+b1.x, a1.y+b1.y)
    case (a1:Odd, b1:Odd) => Odd(a1.x+b1.x, a1.y+b1.y)
    case (a1:Odd, b1:Even) => Odd(a1.x+b1.x, a1.y+b1.y)
    case (a1:All, b1:All) => All(a1.x, b1.x)
    case _ => error("can't add " + a + " to " + b)
  }
}

这里的诀窍是首先将两个参数包装到一个元组中,这样你就可以对一个对象进行模式匹配了。

更新

在您的编辑之后;您似乎在任何地方都不需要抽象类型 A,为什么不将 Num 作为标记特征并在每个子类中分别定义 + 方法?

sealed abstract trait Num

case class Odd(x: Double, y: Double) extends Num {
  def +(a: Odd) = Odd(x + a.x, y + a.y)
}

final case class Even(x: Int, y: Int) extends Num {
  def +(a: Even) = Even(x + a.x, y + a.y)
}

final case class All(x: Double) extends Num {
  def +(a: All) = All(x + a.x)
}

final case class NumHolder(x: Num) extends Num {
  def +(a: NumHolder) = NumHolder(x + a.x)
}

【讨论】:

  • 就是这样!这应该是正确的方法。我已经编辑了我的问题并创建了按照您所说的方式工作的新代码。
  • 好的。那可行。但是当我只使用超类型时,我就没有多态的优势了。有时我必须使用它。为什么使用类型会出现编译错误?
  • 你能举一个例子来说明你如何以及在哪里使用这样的多态性吗?我只能继续你到目前为止所展示的内容。
【解决方案4】:

我不知道我是否能解决你的问题,但在考虑它的同时,我试图至少让你的示例能够编译和工作,希望这至少会有所帮助。

我以toString 方法的形式添加了一些噪音,以便能够查看实例化和表达式的结果。

abstract class Num(val a: Double, val b: Double) {
  def +(that: Num): Num
  override def toString = (<z>Num({a}, {b})</z> text)
}

“最后的手段”类 All 应该是一个案例类,以使匹配更顺畅,但由于它将被继承为真正的案例类,因此效果不佳。带有 applyunapply 方法的伴随对象解决了这个问题。

All 可以处理类似的术语,但不会尝试处理 EvenOdd 术语,因为这些术语同时是秘密的 Alls。

class All(override val a: Double, override val b: Double) extends Num(a, b) {
  def +(that: Num): Num = that match {
    case All(n) => All(this.a + n)
    case _      => error("I don't know this subtype")
  }
  override def toString = (<z>All({a})</z> text)
}
object All {
  def apply(num: Double) = new All(num, num)
  def unapply(num: All) = Some(num.a)
}

现在,Evens 和 Odds 的工作方式可以被提炼成特征,但对于这个例子来说不是必需的。不这样做会简化继承,但可能与示例的要点相悖,我不知道。

Even 知道如何处理 EvenOdd 术语,但会将任何其他术语传递给它的超类。同样,它是一个用于匹配目的的人造案例类。

class Even(override val a: Double, override val b: Double) extends All(a, b) {
  override def +(that: Num): Num = that match {
    case Even(a, b) => Even(this.a + a, this.b + b)
    case Odd(a, b)  => Odd(this.a + a, this.b + b)
    case x => super.+(x)
  }
  override def toString = (<z>Even({a}, {b})</z> text)
}
object Even {
  def apply(a: Double, b: Double) = new Even(a, b)
  def unapply(num: Even) = Some((num.a, num.b))
}

Odd 知道如何处理 Even 条款,但拒绝处理 Odd 条款(我从您的示例中更改了此内容以构成弱双关语,不客气)。

class Odd(override val a: Double, override val b: Double) extends All(a, b) {
  override def +(that: Num): Num = that match {
    case Even(a, b) => Odd(this.a + a, this.b + b)
    case Odd(a, b)  => error("Adding two Odds is an odd thing to do")
    case x => super.+(x)
  }
  override def toString = (<z>Odd({a}, {b})</z> text)
}
object Odd {
  def apply(a: Double, b: Double) = new Odd(a, b)
  def unapply(num: Odd) = Some((num.a, num.b))
}

好的,让我们试一试。

object Try {
  def main(args: Array[String]) {
    val e1 = Even(2, 4)
    val e2 = Even(1, 3)
    val o1 = Odd(1.25, 3.75)
    val o2 = Odd(7.25, 9.25)
    val a1 = All(5.5)
    val a2 = All(3.5)

    println("e1 + e2: " + (e1 + e2))
    println("e1 + o2: " + (e1 + o2))
    try { println("o1 + o2: " + (o1 + o2)) } catch { case e => println(e) }
    println("o1 + e2: " + (o1 + e2))
    println("a1 + e1: " + (a1 + e2))
  }
}

【讨论】:

  • 没有描述的答案中的代码就像没有冰淇淋的苹果派......这是不对的!
  • 当然。 Stackoverflow 好几个小时都无法访问,等我回来的时候,实在是太累了,写不出什么值得一看的东西,所以我基本就发帖睡觉了。对不起。
  • 感谢您的帮助,但我无法在我的实际应用程序中使用您的代码,因为对象之间的差异太大。我不能继承它们。还有一个问题是我必须测试每个组合才能添加对象。
【解决方案5】:

看起来泛型可能会对您有所帮助。试试这样的:

class Supertype[A <: Supertype] {
    def operation(s: A) {

    }
}

class Subtype extends SuperType[Subtype] {
    override def operation(s: Subtype) {

    }
}

您的问题描述不是很清楚,所以这是一个猜测...

【讨论】:

  • 感谢您的回答,但这不起作用,因为对于泛型,我在子类中只有一种类型。我需要像“def operation(s: SuperType)”这样的东西,因为我不知道我有什么样的类型。类必须对不同的类型做出反应。
【解决方案6】:

基于 Kevin Wright 的 answer 我现在解决了这个问题:

package de.traitwith

import de.traitwith.Operations._
import de.traitwith._

object TraitWithTest {
  def main(args: Array[String]) {
    val e1 = Even(2, 4)
    val e2 = Even(1, 3)
    val o1 = Odd(1.25, 3.75)
    val o2 = Odd(7.25, 9.25)
    val a1 = All(5.5)
    val a2 = All(3.5)
    val n1 = NumHolder(o1)
    val n2 = NumHolder(a1)
    println("e1 + e2: " + add(e1, e2))
    println("o1 + o2: " + add(o1, o2))
    try { println("e1 + o2: " + add(e1, o2)) } catch { case e => println(e) }
    println("o1 + e2: " + add(o1, e2))
    try { println("a1 + e2: " + add(a1, e2)) } catch { case e => println(e) }
    println("n1 + n2: " + add(n1, n2))
    println("o1 + n2: " + add(o1, n2))
  }
}

object Operations {
  def add(a: Num, b: Num): Num = a -> b match {
    case (a1: Odd, b1: Odd) => a1 + b1
    case (a1: Odd, b1: Even) => Odd(a1.x + b1.x, a1.y + b1.y)
    case (a1: Odd, b1: All) => Odd(a1.x + b1.x, a1.y + b1.x)
    case (a1: Odd, b1: NumHolder) => add(a1, b1.x)
    case (a1: Even, b1: Even) => a1 + b1
    case (a1: Even, b1: NumHolder) => add(a1, b1.x)
    case (a1: All, b1: All) => a1 + b1
    case (a1: All, b1: NumHolder) => add(a1, b1.x)
    case (a1: NumHolder, b1: NumHolder) => a1 + b1
    case (a1: NumHolder, b1: Odd)=> add(a1.x, b1)
    case (a1: NumHolder, b1: Even) => add(a1.x, b1)
    case (a1: NumHolder, b1: All) => add(a1.x, b1)
    case _ => error("can't add " + b + " to " + a)
  }
}

abstract class Num

final case class Odd(x: Double, y: Double) extends Num {
  def +(a: Odd) = Odd(x + a.x, y + a.y)
}

final case class Even(x: Int, y: Int) extends Num {
  def +(a: Even) = Even(x + a.x, y + a.y)
}

final case class All(x: Double) extends Num {
  def +(a: All) = All(x + a.x)
}

final case class NumHolder(x: Num) extends Num {
  def +(a: NumHolder) = NumHolder(add(x, a.x))
}

我没有更多超类型中的一些方法 - 我希望在同一天,这不会造成问题。可以删除类中的所有添加方法,但在我的实际应用程序中,我有更大的类,所以我需要它们。

【讨论】:

    猜你喜欢
    • 2019-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-16
    • 2019-01-26
    • 2012-11-16
    • 2016-05-17
    • 1970-01-01
    相关资源
    最近更新 更多