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 another instance of calculus for each lattice (the operations that I will perform are necessary, information about which is the maximum and minimum values โ€‹โ€‹of the lattice). I want to be able to mix expressions of the same calculus, but not to mix different expressions. So far, so good. I can create my own calculus instances, but the problem is that I cannot write functions in other classes that manipulate them.

For example, I'm trying to create a parser to read expressions from a file and return them; I also tried to write a random expression generator for use in my tests with ScalaCheck. It turns out that every time a function generates an Expr object, I cannot use it outside the function. Even if I create an instance of Calculus and pass it as an argument to the function, which in turn will generate Expr objects, the return of the function will not be recognized as the same type of objects created outside the function.

Perhaps my English is not clear enough, let me try a toy example of what I would like to do (not a 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 a lot

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

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

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

Note that the generator itself is not a concern, but I will need to do such things (i.e. create and process expressions of a calculus instance) a lot in the rest of the system.

Am I doing something wrong? Can I do what I want to do?

Help on this issue is very necessary and appreciated. Thank you very much in advance.


After receiving the response from Apocalisp and try it.

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

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

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

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

Since the compiler did not seem to be able to determine the correct types, I changed all the function calls as below:

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

After that, the first 2 calls to the function (genRndVal and genRndVar) did not compile, and the next 3 calls (recursive calls to genRndExpr), where the function return is used to build a new Expr object, I received 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 would be appreciated.

+6
scala module functor type-inference inner-classes
source share
2 answers

The problem is that Scala cannot combine the two types Calculus[E]#Expr and Calculus[E]#Expr .

The ones look the same to you, right? Well, think that you can have two different calculi over some type E , each with its own type Expr . And you do not want to mix the expressions of the two.

You need to restrict the types so that the return type is the same Expr type as the internal Expr type of your Calculus argument. What you need to do is:

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

If you do not want to get a specific calculus from Calculus, simply move Expr to the global area or specify it in the global area:

 class Calculus[E] { abstract class Expression final type Expr = Calculus[E]#Expression ... the rest like in your code } 

this question refers to the same problem.

If you want to make a subtype of Calculus and override Expr there (which is unlikely), you should:

put getRndExpr in the Calculus class or put getRndExpr in a derived line:

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

send this stream for the reason why so.

+1
source share

All Articles