Я хотел бы знать, каковы недостатки/преимущества каждого определения в отношении соответствия шаблонов?
Ответ 2
Позвольте ответить на различные вопросы, которые здесь обсуждаются. Я бы действительно рекомендовал этот синтаксис:
case class ExpList(listExp: Expr*) extends Expr
Но ответ зависит от вашего примера кодирования. Поэтому давайте посмотрим, как использовать varargs в сопоставлении с образцом, когда использовать List
и проблему с WrappedArray.
Небольшое замечание: несоответствие между Expr
и ExpList
(с или без 'r'?) Проблематично при наборе текста и попытке вспомнить, что есть - придерживаться одного соглашения, Exp
достаточно ясно и часто используется.
Варвар и сопоставление образцов
Давайте сначала рассмотрим это выражение:
abstract class Expr
case class ExpList(listExp: Expr*) extends Expr
case class Var(name: String) extends Expr
И этот пример кода:
val v = Var("a")
val result = for (i <- Seq(ExpList(v), ExpList(v, v), ExpList(v, v, v))) yield (i match {
case ExpList(a) => "Length 1 (%s)" format a
case ExpList(a, b, c, d @ _*) => "Length >= 3 (%s, %s, %s, %s...)" format (a, b, c, d)
case ExpList(args @ _*) => "Any length: " + args
})
result foreach println
дает:
Length 1 (Var(a))
Any length: WrappedArray(Var(a), Var(a))
Length >= 3 (Var(a), Var(a), Var(a), WrappedArray()...)
Что я здесь использую:
ExpList(a, b)
соответствует ExpList с двумя детьми; ExpList(a)
соответствует ExpList с одним дочерним элементом.
_*
- это шаблон, который соответствует последовательностям значений типа A
, которые могут быть сколь угодно длинными (включая 0).
Я также использую привязки шаблонов, identifier @ pattern
, которые позволяют связывать объект, а также дополнительно разрушать его другим шаблоном; они работают с любым шаблоном, а не только с _*
.
При использовании identifier @ _*
identifier
привязывается к типу Seq[A]
.
Все эти конструкции также применимы к Seq
; но если мы используем Seq
в объявлении, например:
case class ExpList(listExp: Seq[Expr]) extends Expr
те же предложения case меняются от (например) case ExpList(a, b, c, d @ _*) =>
до case ExpList(Seq(a, b, c, d @ _*)) =>
. Таким образом, более синтаксический беспорядок.
Синтаксически говоря, единственное, что "сложнее" с Expr*
заключается в написании следующей функции, которая строит ExpList из списка выражений:
def f(x: Seq[Expr]) = ExpList(x: _*)
Обратите внимание на использование (снова) _*
здесь.
Класс List
List
удобно использовать, когда вы сопоставляете шаблон с конструктором списка, как в xs match { case head :: tail => ... case Nil => }
. Однако обычно этот код можно выразить более компактно, используя складки, и если вы не пишете код в этом стиле, вам не нужно использовать List
. Особенно в интерфейсе часто бывает, что требуется только то, что понадобится вашему коду.
Изменчивость
То, о чем мы говорили выше, касалось неизменности. Экземпляры классов дел должны быть неизменными. Теперь при использовании Expr*
параметр класса case фактически имеет тип collection.Seq[Expr]
, и этот тип содержит изменяемые экземпляры - фактически, ExprList получит экземпляр подкласса WrappedArray
, который является изменяемым. Обратите внимание, что collection.Seq
является суперклассом как collection.mutable.Seq
, так и collection.immutable.Seq
, а последний по умолчанию имеет псевдоним Seq
.
Нельзя мутировать такое значение, не опуская его, но это возможно для кого-то (я не знаю, по какой причине).
Если вы хотите, чтобы ваш клиент не выполнял его, вам нужно преобразовать полученное значение в неизменяемую последовательность, но вы не можете сделать это при объявлении ExpList с помощью case class ExpList(listExp: Expr*) extends Expr
.
Вам нужно использовать другой конструктор.
Чтобы сделать преобразование в другом коде, так как toSeq
возвращает исходную последовательность, вы должны вызвать конструктор Seq
с содержимым списка как переменные аргументы. Следовательно, вы используете синтаксис, показанный выше, Seq(listExpr: _*)
.
В настоящее время это не так важно, поскольку Seq
реализация по умолчанию - List
, но это может измениться в будущем (может быть, что-то быстрее, кто знает?).
Проблемы с стиранием
Нельзя объявить две перегрузки одного и того же метода: один принимает T*
и принимает Seq[T]
, потому что в выходном классе они станут одинаковыми. Небольшой трюк, чтобы заставить m выглядеть по-другому и использовать два конструктора:
case class ExpList(listExp: Seq[Expr]) extends Expr
object ExpList {
def apply(listExp: Expr*)(implicit d: DummyImplicit) = new ExpList(Seq(listExp: _*))
}
Здесь я также преобразую массив в неизменяемую последовательность, как указано выше. Соответствие шаблонов выполняется, к сожалению, как в примере выше, где класс case принимает Seq[Expr]
вместо Expr*
.