Search code examples
scalaabstract-data-type

Method Definition When Using Algebraic Data Type in Scala


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


Solution

  • 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.