Hello,
I have some classes with the same super-type. Therefore all of this classes have to override the same methods. Now I can call a method and commit it an object of the common super-type. But it is not always useful to react to each committed type therefore an exception is thrown. First i tried to solve this behaviour like this:
def operation(s: SuperType) = s match {
case t: SubType1 => ...
case t: SubType2 => ...
case _ => ...
}
Due to a lot of sub-types this will result to a lot of code (in each method and in each class) and I tried to solve this problem with traits. Each trait should test only one type and then forward the object to the higher method on the stack. The code below describes how I imagine that. But this don't work because the compiler can't dissolve the types. An other problem is that I have to declare each attribute of the classes in each behaviour class.
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
}
}
Can someone give say me whether it is possible to reduce lines of code by using traits? Or is the match-all-solution I currently using the best?
EDIT:
With your help I found a half-working-solution. My main-problem was that I tried to reduce the lines of code by using Scala-features. So I overlooked the easiest way: outsourcing the code! I only have to create a new object which checks the object-combinations. The objects themselves handle only their own types.
This is the code:
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)
}
I extended the code a bit and inserted the object NumHolder. Now, there is only one little flaw: In NumHolder I can't commit the super-type without getting a compile error in the add-method. I tried to use Generics instead of the type-keyword but that is unhandy because I have always to set a type to Num (also in the object operations).
How can I solve this little compile error?