Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use objects as modules/functors in Scala?

I want to use object instances as modules/functors, more or less as shown below:

abstract class Lattice[E] extends Set[E] {
  val minimum: E
  val maximum: E
  def meet(x: E, y: E): E
  def join(x: E, y: E): E
  def neg(x: E): E
}

class Calculus[E](val lat: Lattice[E]) {
  abstract class Expr
  case class Var(name: String) extends Expr {...}
  case class Val(value: E) extends Expr {...}
  case class Neg(e1: Expr) extends Expr {...}
  case class Cnj(e1: Expr, e2: Expr) extends Expr {...}
  case class Dsj(e1: Expr, e2: Expr) extends Expr {...}
}

So that I can create a different calculus instance for each lattice (the operations I will perform need the information of which are the maximum and minimum values of the lattice). I want to be able to mix expressions of the same calculus but not be allowed to mix expressions of different ones. So far, so good. I can create my calculus instances, but problem is that I can not write functions in other classes that manipulate them.

For example, I am trying to create a parser to read expressions from a file and return them; I also was trying to write an random expression generator to use in my tests with ScalaCheck. Turns out that every time a function generates an Expr object I can't use it outside the function. Even if I create the Calculus instance and pass it as an argument to the function that will in turn generate the Expr objects, the return of the function is not recognized as being of the same type of the objects created outside the function.

Maybe my english is not clear enough, let me try a toy example of what I would like to do (not the real ScalaCheck generator, but close enough).

def genRndExpr[E](c: Calculus[E], level: Int): Calculus[E]#Expr = {
  if (level > MAX_LEVEL) {
    val select = util.Random.nextInt(2)
    select match {
      case 0 => genRndVar(c)
      case 1 => genRndVal(c)
    }
  }
  else {
    val select = util.Random.nextInt(3)
    select match {
      case 0 => new c.Neg(genRndExpr(c, level+1))
      case 1 => new c.Dsj(genRndExpr(c, level+1), genRndExpr(c, level+1))
      case 2 => new c.Cnj(genRndExpr(c, level+1), genRndExpr(c, level+1))
    }
  }
}

Now, if I try to compile the above code I get lots of

 error: type mismatch;  
 found   : plg.mvfml.Calculus[E]#Expr  
 required: c.Expr  
        case 0 => new c.Neg(genRndExpr(c, level+1))  

And the same happens if I try to do something like:

val boolCalc = new Calculus(Bool)
val e1: boolCalc.Expr = genRndExpr(boolCalc)

Please note that the generator itself is not of concern, but I will need to do similar things (i.e. create and manipulate calculus instance expressions) a lot on the rest of the system.

Am I doing something wrong? Is it possible to do what I want to do?

Help on this matter is highly needed and appreciated. Thanks a lot in advance.


After receiving an answer from Apocalisp and trying it.

Thanks a lot for the answer, but there are still some issues. The proposed solution was to change the signature of the function to:

def genRndExpr[E, C <: Calculus[E]](c: C, level: Int): C#Expr

I changed the signature for all the functions involved: getRndExpr, getRndVal and getRndVar. And I got the same error message everywhere I call these functions and got the following error message:

error: inferred type arguments [Nothing,C] do not conform to method genRndVar's 
type parameter bounds [E,C <: plg.mvfml.Calculus[E]]
        case 0 => genRndVar(c)

Since the compiler seemed to be unable to figure out the right types I changed all function call to be like below:

case 0 => new c.Neg(genRndExpr[E,C](c, level+1))

After this, on the first 2 function calls (genRndVal and genRndVar) there were no compiling error, but on the following 3 calls (recursive calls to genRndExpr), where the return of the function is used to build a new Expr object I got the following error:

error: type mismatch;
 found   : C#Expr
 required: c.Expr
        case 0 => new c.Neg(genRndExpr[E,C](c, level+1))

So, again, I'm stuck. Any help will be appreciated.

like image 302
Jeff Avatar asked Apr 09 '10 21:04

Jeff


2 Answers

The problem is that Scala is not able to unify the two types Calculus[E]#Expr and Calculus[E]#Expr.

Those look the same to you though, right? Well, consider that you could have two distinct calculi over some type E, each with their own Expr type. And you would not want to mix expressions of the two.

You need to constrain the types in such a way that the return type is the same Expr type as the Expr inner type of your Calculus argument. What you have to do is this:

def genRndExpr[E, C <: Calculus[E]](c: C, level: Int): C#Expr
like image 125
Apocalisp Avatar answered Sep 20 '22 11:09

Apocalisp


If you don't want to derive a specific calculus from Calculus then just move Expr to global scope or refer it through global scope:

class Calculus[E] {
    abstract class Expression
    final type Expr = Calculus[E]#Expression

    ... the rest like in your code
}

this question refers to exactly the same problem.

If you do want to make a subtype of Calculus and redefine Expr there (what is unlikely), you have to:

put getRndExpr into the Calculus class or put getRndExpr into a derived trait:

 trait CalculusExtensions[E] extends Calculus[E] { 
     def getRndExpr(level: Int) = ...
     ...
 }

refer this thread for the reason why so.

like image 27
Alexey Avatar answered Sep 21 '22 11:09

Alexey