I want to implement a Library in Scala. I'm just starting and i'm already having trouble to design it in a modular and scalable way.
I need some help! For instance i have defined an tree ADT
sealed trait Tree[+A,+B,+C]
case object EmptyTree extends Tree[Nothing, Nothing, Nothing]
case class Leaf[A,B,C](value: C) extends Tree[A,B,C]
case class Branch_A1[A,B,C](op: B, left: Tree[A,B,C]) extends Tree[A,B,C]
case class Branch_A2[A,B,C](op: A, left: Tree[A,B,C], right: Tree[A,B,C]) extends Tree[A,B,C]
The Branch_A# represents a function with # arguments. You can see where this is going.
Why A,B and C? Because the Leafs will be e.g. Double, and i have two types of functions Double => Double and (Double, Double) => Double. I'm worried about how this will scale as I increase the number diversity of the branches. I w.anted to make this very flexible
I have two questions, one technical, the other regarding my design pattern.
Technical Question
When i try to define generic methods to operate on such structures i get compile errors which I cannot solve. For instance:
sealed trait Tree[+A,+B,+C] {
def evaluateTree[A,B,C] (t: Tree[A,B,C]): C = t match {
case Leaf(value) => value
case Branch_A1(op, left) => op(evaluateTree(left))
case Branch_A2(op, left, right) => op(evaluateTree(left),evaluateTree(right))
}
}
case object EmptyTree extends Tree[Nothing, Nothing, Nothing]
case class Leaf[A,B,C](value: C) extends Tree[A,B,C]
case class Branch_A1[A,B,C](op: B, left: Tree[A,B,C]) extends Tree[A,B,C]
case class Branch_A2[A,B,C](op: A, left: Tree[A,B,C], right: Tree[A,B,C]) extends Tree[A,B,C]
In op(evaluateTree(left))
i get "Application does not support parameters". I cant understand why.
Design Question
If the user of the library is to be allowed to express a domain which higher arity functions this will be hard to manage. The number of generics types will explode i guess. How can i design this in a better way? I wanted to make this scalable. Is the use of generic types the most proper? Or should another way? I read that about abstract data types are an alternative
To the Technical Question: the compiler doesn't have enough information about A
or B
to know if op(...)
is possible/permitted or that, if invoked, it would return the correct type.
What if (to use a simpler example) you had written case Leaf(value) => value-1
? The compiler isn't going to allow that until/unless C
has been defined/restricted to some subset of types known to have a -(x:Int)
method with a matching return type.