Как использовать объекты в качестве модулей / функторов в Scala? - PullRequest
6 голосов
/ 10 апреля 2010

Я хочу использовать экземпляры объектов в качестве модулей / функторов, более или менее как показано ниже:

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

После этого в первых двух вызовах функций (genRndVal и genRndVar) не было ошибок компиляции, но в следующих трех вызовах (рекурсивные вызовы genRndExpr), где возврат функции используется для создания нового объекта Expr Я получил следующую ошибку:

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

Итак, я опять застрял. Любая помощь будет оценена.

Ответы [ 2 ]

3 голосов
/ 10 апреля 2010

Проблема в том, что Scala не может объединить два типа Calculus[E]#Expr и Calculus[E]#Expr.

Тем не менее, они выглядят одинаково, верно? Хорошо, учтите, что у вас может быть два разных исчисления для некоторого типа E, каждое из которых имеет свой собственный тип Expr. И вы не хотели бы смешивать выражения двух.

Вам необходимо ограничить типы таким образом, чтобы возвращаемый тип был того же типа Expr, что и внутренний тип Expr аргумента Calculus. Что вам нужно сделать, это:

def genRndExpr[E, C <: Calculus[E]](c: C, level: Int): C#Expr
1 голос
/ 10 апреля 2010

Если вы не хотите извлекать конкретное исчисление из исчисления, просто переместите Expr в глобальную область или направьте его через глобальную область:

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

    ... the rest like in your code
}

этот вопрос относится к точно такой же проблеме.

Если вы хотите создать подтип Calculus и переопределить Expr там (что маловероятно), вы должны:

поместите getRndExpr в класс Calculus или поместите getRndExpr в производную черту:

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

см. эту ветку по причине, по которой это так.

...