如何在Scala中将对象用作模块/仿函数?

时间:2010-04-09 21:29:57

标签: scala module type-inference inner-classes functor

我想将对象实例用作模块/仿函数,或多或少,如下所示:

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 {...}
}

这样我就可以为每个晶格创建一个不同的微积分实例(我将执行的操作需要其中的信息是晶格的最大值和最小值)。我希望能够混合相同微积分的表达式,但不允许混合不同表达式的表达式。到现在为止还挺好。我可以创建我的微积分实例,但问题是我不能在其他操作它们的类中编写函数。

例如,我正在尝试创建一个解析器来读取文件中的表达式并返回它们;我也试图编写一个随机表达式生成器,用于我的ScalaCheck测试。事实证明,每次函数生成Expr对象时,我都无法在函数外部使用它。即使我创建了Calculus实例并将其作为参数传递给将依次生成Expr对象的函数,函数的返回也不会被识别为与函数外部创建的对象类型相同。

也许我的英语不够清楚,让我尝试一下我想做的玩具示例(不是真正的ScalaCheck发生器,但足够接近)。

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))
    }
  }
}

现在,如果我尝试编译上面的代码,我会得到很多

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

如果我尝试做类似的事情,也会发生同样的事情:

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

请注意,生成器本身并不重要,但我需要在系统的其余部分做很多类似的事情(即创建和操作微积分实例表达式)。

我做错了吗? 是否有可能做我想做的事情?

非常需要和赞赏这方面的帮助。非常感谢。


收到Apocalisp的答案并尝试后。

非常感谢答案,但仍有一些问题。建议的解决方案是将函数的签名更改为:

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

我更改了所涉及的所有函数的签名:getRndExpr,getRndVal和getRndVar。我在调用这些函数时得到了相同的错误消息,并收到以下错误消息:

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)

由于编译器似乎无法找出正确的类型,我将所有函数调用更改为如下所示:

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

在此之后,在前2个函数调用(genRndVal和genRndVar)上没有编译错误,但在以下3个调用(对genRndExpr的递归调用)中,函数的返回用于构建新的Expr对象我收到以下错误:

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

所以,再一次,我被困住了。任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:3)

问题是Scala无法统一Calculus[E]#ExprCalculus[E]#Expr两种类型。

那些看起来和你一样,对吧?好吧,考虑到某种类型E可能有两个不同的结石,每个类型都有自己的Expr类型。而且你不想混合两者的表达。

您需要以这样的方式约束类型:返回类型与Expr参数的Expr内部类型相同Calculus类型。你要做的是:

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

答案 1 :(得分:1)

如果您不想从微积分中导出特定的微积分,那么只需将Expr移到全局范围或通过全局范围引用它:

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

    ... the rest like in your code
}

this question指的是完全相同的问题。

如果你想制作微积分的子类型并在那里重新定义Expr(不太可能),你必须:

将getRndExpr放入Calculus类或将getRndExpr放入派生特征:

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

请参考this主题,了解原因。