Замена наследования класса дел на экстракторы с сохранением исчерпывающих проверок в Scala - PullRequest
9 голосов
/ 13 июня 2011

У меня есть простая иерархия классов, которая представляет собой графоподобную структуру с несколькими различными типами вершин, реализованными с использованием классов case:

sealed trait Node

sealed abstract case class Vertex extends Node
case class Arc extends Node

case class VertexType1 (val a:Int) extends Vertex
case class VertexType2 (val b:Int) extends Vertex

Это позволяет мне писать блоки совпадений следующим образом:

def test (x: Node) = x match {
  case _ : Arc => "got arc"
  case _ : Vertex => "got vertex"
}

или как это:

def test (x: Node) = x match {
  case _ : Arc => "got arc"
  case c : Vertex => c match {
    case _ : VertexType1(a) => "got type 1 vertex " + a
    case _ : VertexType2(a) => "got type 2 vertex " + a
  }
}

Обратите внимание, что эта реализация имеет следующие свойства:

1) Позволяет записывать блоки соответствия, которые различают дуги и вершины, но не между конкретными типами вершин, но также и блоки соответствия, которые различают типы вершин.

2) В блоках сопоставления, характерных как для типа вершины, так и для типа, отличного от типа вершины, проверяется полнота сопоставления с образцом.

Однако наследование от case-классов устарело, и компилятор предлагает вместо этого использовать экстракторы для поддержки сопоставления на неконечных узлах (т. Е. В приведенном выше примере, чтобы различать дуги и вершины, но не типы вершин).

Вопрос: возможно ли реализовать аналогичную иерархию классов, не используя наследование классов дел, но при этом все еще имея проверки исчерпываемости шаблонов, выполняемые компилятором в обоих случаях использования, показанных выше?

РЕДАКТИРОВАТЬ : Я добавил параметр конструктора в классы VertexType, чтобы сопоставление не выполнялось только для типов.

Моя текущая реализация без классов case выглядит следующим образом:

sealed trait Node

sealed abstract class Vertex extends Node
class Arc extends Node

class VertexType1 (val a:Int) extends Vertex
class VertexType2 (val b:Int) extends Vertex

object VertexType1 {
  def unapply (x : VertexType1) : Some[Int] = Some(x.a)
}

object VertexType2 {
  def unapply (x : VertexType2) : Some[Int] = Some(x.b)
}

И тестовый код:

def test (x: Node) = x match {
  case _ : Arc => "got arc" 
  case v : Vertex => v match {
    case VertexType1(a) => "got vertex type 1 " + a 
  }
}

Я ожидаю предупреждения о неисчерпывающем совпадении во втором блоке (VertexType2 никогда не сопоставляется), но его нет.

На самом деле, компиляторы Scala до 2.9.0-RC3 выдают предупреждение, которое я ожидаю увидеть, но версии, начинающиеся с RC3 (включая 2.9.0 и 2.9.0-1), этого не делают, что довольно запутанно.

Ответы [ 3 ]

2 голосов
/ 13 июня 2011

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

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

То, что вы сделали в своем примере, называется сопоставлением с образцом для типов. Если вы только хотите это сделать, вам не нужны ни case-классы, ни экстракторы. Вы можете сделать это с любым классом, который вы хотите. Таким образом, вы можете просто удалить модификатор case.

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

2 голосов
/ 14 июня 2011

Как правило, это невозможно.

Запечатанные классы являются особым случаем (без каламбура), поскольку scalac знает во время компиляции, сколько совпадений возможно.

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

def test(foo: Int) {
  foo match {
    case IsMultipleOf8(n) => printf("%d was odd\n", n)
  }
}

Это не является исчерпывающим, потому что он не обрабатывает числа, которые не кратны 8, но компилятор не может вывести (без запуска вашего экстрактора на всех Int), что тольконекоторые значения типа Int кратны 8.

0 голосов
/ 13 июня 2011

Цитата из scala-lang.org:

Если селектор сопоставления с образцом является экземпляром закрытого класса, компиляция сопоставления с образцом может выдавать предупреждения, которые диагностируют данный наборшаблонов не является исчерпывающим, то есть существует вероятность возникновения ошибки MatchErbebeing во время выполнения.

...