Связанный список состоит из двух случаев: ячейка минусов (с головой и хвостом) и пустой список.Эти случаи называются ::
и Nil
соответственно в Scala, как и в других языках с наследием ML.В неизменяемой реализации наиболее естественная кодировка выглядит примерно так:
sealed trait List[+A]
case class Cons[+A](head: A, tail: List[A]) extends List[A]
case object Nil extends List[Nothing]
Это прекрасно работает, и в этом нет ничего плохого.Вы можете определить метод ::
для List
(или prepend
, если хотите), и все будет работать так, как вы ожидаете.В этом случае «конкретная реализация» List
будет Cons
, хотя технически и Cons
, и Nil
являются конкретными реализациями.
Но вот проблема: мы хотим иметь возможностьсопоставление с образцом в экземпляре List
для возврата содержимого.В сложившейся ситуации для такого сопоставления с образцом потребуется следующий синтаксис:
def sum(xs: List[Int]): Int = xs match {
case Cons(hd, tl) => hd + sum(tl)
case Nil => 0
}
Хотя это немного уродливо.Что мы действительно хотим, так это иметь возможность использовать оператор ::
в нашем сопоставлении с образцом точно так же, как мы используем его при первоначальном построении списка.Вот почему класс Cons
назван ::
.
Определяя класс case из двух параметров, мы в основном сообщаем Scala, что хотим, чтобы он позволил нам использовать этот класс case в качестве инфикса.экстрактор в сопоставлении с образцом.Завершающая двоеточие делает этот экстрактор справа ассоциативным, давая нам ожидаемый синтаксис:
sealed trait List[+A]
case class ::[+A](head: A, tail: List[A]) extends List[A]
case object Nil extends List[Nothing]
def sum(xs: List[Int]): Int = xs match {
case hd :: tl => hd + sum(tl)
case Nil => 0
}
Именно поэтому любой экземпляр List
, имеющий содержимое, будет экземпляром класса ::
, так как этонепустая реализация List
.