如何在 Scala 中使用对象作为模块/函子?

发布于 2024-08-28 07:26:58 字数 2740 浏览 3 评论 0 原文

我想使用对象实例作为模块/函子,或多或少如下所示:

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

:我被困住了。任何帮助将不胜感激。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

残花月 2024-09-04 07:26:58

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

但这些对你来说看起来是一样的,对吧?好吧,考虑一下您可能对某种类型 E 有两个不同的演算,每个演算都有自己的 Expr 类型。而且你不会想混合这两种表达方式。

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

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

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
oО清风挽发oО 2024-09-04 07:26:58

如果您不想从微积分中派生特定的微积分,则只需将 Expr 移至全局范围或通过全局范围引用它:

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

    ... the rest like in your code
}

这个问题指的是完全相同的问题。

如果您确实想创建微积分的子类型并在那里重新定义 Expr(这不太可能),您必须:

将 getRndExpr 放入微积分类中或将 getRndExpr 放入派生特征中:

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

请参阅 这个 线程为什么会这样。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文