Scala 2.9: подкласс Parsers не распознает переопределение Elem?

Я написал парсер, который действует как лексер. Этот лексер анализирует файл и возвращает список токенов, каждый из которых является классом case или объектом, расширяющим общий признак.

Сейчас я пытаюсь написать парсер для вывода лексера, но столкнулся с очень запутанной загвоздкой. Синтаксический анализатор счастлив неявно преобразовать мои объекты case, но испортится, если я даже попытаюсь вызвать apply(classHere) вручную.

Ниже приведена упрощенная версия моего кода:

// CODE
trait Token

case class StringWrapperIgnoresCase(val string: String) {
  private case class InnerWrapper(s: String)

  lazy val lower = string.toLowerCase

  override lazy val hashCode = InnerWrapper(lower).hashCode

  override def equals(that: Any) =
    that.isInstanceOf[StringWrapperIgnoresCase] &&
      lower == that.asInstanceOf[StringWrapperIgnoresCase].lower
}

case class ID(val text: String)
  extends StringWrapperIgnoresCase(text)
  with Token {
    override def toString = "ID(" + text + ")"
  }

case object PERIOD extends Token

object Parser extends Parsers {
  type Elem = Token

  def doesWork: Parser[Token] = PERIOD

  def doesNotWork: Parser[Token] = ID
}

Компилятор сообщает следующее сообщение о doesNotWork:

// ERROR MESSAGE
type mismatch;  found   : alan.parser.ID.type (with underlying type object alan.parser.ID)  required: alan.parser.Parser.Parser[alan.parser.Token]

Как я могу это исправить?


person sadakatsu    schedule 29.11.2012    source источник


Ответы (2)


Обновление: из вашего вопроса мне было непонятно, что именно вы спрашивали, но теперь, когда вы указали, что вам нужен синтаксический анализатор, который соответствует любому ID в вашем ответе, вот более идиоматическое решение:

val id: Parser[ID] = accept("ID", { case i: ID => i })

Здесь вы предоставили описание того, что хочет синтаксический анализатор (для сообщений об ошибках), и частичную функцию с IDs в качестве домена. Вы также можете использовать версию acceptIf, которую xiefei предоставляет в комментарии к вашему ответу.


Когда вы ссылаетесь на класс case (в отличие от объекта case) без списка параметров, вы получаете автоматически сгенерированный сопутствующий объект, который не является экземпляром самого класса. Рассмотрим следующее:

sealed trait Foo
case class Bar(i: Int) extends Foo
case object Baz extends Foo

Теперь Baz: Foo все в порядке, но Bar: Foo выдаст ошибку, очень похожую на то, что вы видите.

Также обратите внимание, что здесь происходит не совсем приведение. Трейт Parsers имеет метод со следующей сигнатурой:

implicit def accept(e: Elem): Parser[Elem]

Когда вы пишете это:

def doesWork: Parser[Token] = PERIOD

Вы пытаетесь ввести Elem вместо Parser[Elem], и срабатывает неявное преобразование (см. раздел 7.3 спецификацию для получения дополнительной информации о неявных преобразованиях). Когда вы пишете это, с другой стороны:

def doesNotWork: Parser[Token] = ID

Вы пытаетесь ввести объект-компаньон ID (который имеет тип ID.type, а не ID или Token и, следовательно, не Elem) как Parser[Elem], и нет неявного преобразования, которое делает это возможным.

Вам, вероятно, лучше записать accept(PERIOD) и accept(ID("whatever")), по крайней мере сейчас, и соблюдать предупреждение об устаревании, которое говорит следующее, когда вы пытаетесь скомпилировать свой код:

Наследование от случая к случаю содержит потенциально опасные ошибки, которые вряд ли будут исправлены.

person Travis Brown    schedule 29.11.2012
comment
drstevens, вы удалили свой ответ, прежде чем я успел задать вам вопрос. Почему я не должен наследовать от case-классов? Должен признаться, тема, на которую вы ссылаетесь, меня смутила ›_› - person sadakatsu; 29.11.2012

Используя то, что сказали TravisBrown и drstevens, я добавил в синтаксический анализатор новую продукцию:

def id = {
  acceptIf(
    _ match {
      case ID(_) => true
      case _ => false
    }
  )("'ID(_)' expected but " + _ + " found")
}

def nowWorks = id

Я не буду принимать это как ответ на данный момент, чтобы позволить кому-то предложить более элегантное решение, чем это. На мой вкус это выглядит немного запутанно, и я уверен, что кто-то, более привыкший к подходу функционального программирования, превратит это в элегантную однострочную строку.

person sadakatsu    schedule 29.11.2012
comment
acceptIf(ID.unapply(_).isDefined)(...). И вы должны принять ответ Трэвиса Брауна. Он указал на проблему в вашем коде на уровне языка и предложил решение. Вы не упомянули логику (которая проявляется в этом), которую вы действительно хотите в своем вопросе, и не должны ожидать, что другие будут читать мысли. - person xiefei; 29.11.2012
comment
Попробовал это после просмотра сообщения, но это не компилируется. Я все еще могу принять ответ Трэвиса Брауна, но я не думаю, что ожидал от кого-либо чтения мыслей. Я сказал, что мой Parser принимает вывод лексера, и показал пару примеров случаев Token, которые он возвращает. Жестко закодированные значения для идентификатора, как это сделал Трэвис, означают, что синтаксический анализатор может сопоставить только этот один идентификатор! - person sadakatsu; 29.11.2012