Как сопоставление с образцом Scala Cons определяет начало и конец списка?
Как определяется голова и хвост в следующем утверждении:
val head::tail = List(1,2,3,4);
//head: 1 tail: List(2,3,4)
Не должно быть некоторого фрагмента кода, который извлекает первый элемент как заголовок и возвращает хвост как новый список. Я просматривал код стандартной библиотеки Scala и не могу найти / понять, как и где это делается.
1 ответ
Используемая здесь конструкция Scala - это экстрактор. Символ ::
это не что иное, как класс в Scala, где unapply
метод существует на его сопутствующем объекте, чтобы волшебство извлечения произошло. Вот хороший подробный учебник по экстракторам. Но вот резюме:
Всякий раз, когда вы хотите "распаковать" содержимое класса, либо для привязки переменной, либо как часть сопоставления с образцом, компилятор ищет метод unapply
на любой символ находится на левой стороне выражения. Это может быть объект, объект-компаньон класса case (например, ::
в вашем вопросе), или экземпляр с unapply
, Аргумент к unapply
это входящий тип для распаковки, а возвращаемый тип Option
из того, что было объявлено в качестве ожидаемой структуры и типов. В соответствии с шаблоном None
указывает, что совпадение не найдено. В переменной привязки MatchError
брошен, если None
это результат.
Хороший способ думать о unapply
является то, что это обратное apply
, куда unapply
получатель синтаксиса вызова функции, unapply
является получателем экстракторных звонков.
Чтобы проиллюстрировать это далее, давайте определим простой класс case:
case class Cat(name: String, age: Int)
Поскольку это класс case, мы автоматически генерируем apply
а также unapply
методы объекта-компаньона, которые примерно выглядят так:
object Cat {
// compiler generated...
def apply(name: String, age: Int) = new Cat(name, age)
def unapply(aCat: Cat): Option[(String, Int)] = Some((aCat.name, aCat.age))
}
Когда вы создаете Cat
через объект-компаньон, apply
называется. Когда вы распаковываете составные части Cat
, unapply
называется:
val mycat = Cat("freddy", 3) // `apply` called here
...
val Cat(name, age) = mycat // `unapply` called here
...
val animal: AnyRef = mycat
val info = animal match {
case Cat(name, age) => "My pet " + name // `unapply` called here
case _ => "Not my pet"
}
// info: String = My pet freddy
Так как unapply
возвращает Option
у нас есть много возможностей для написания экстракторов, которые обрабатывают более интересные случаи, например, проверяя, соответствует ли входящий тип некоторым критериям, перед извлечением значений. Например, допустим, мы хотим получить название кошек, которые являются "старыми". Можно сделать это:
object OldCatName {
def unapply(aCat: Cat) = if (aCat.age >= 10) Some(aCat.name) else None
}
Использование будет таким же, как сгенерированный unapply
:
val yourcat = Cat("betty", 12)
...
val OldCatName(name1) = yourcat
// name1: String = "betty"
val OldCatName(name2) = mycat
// scala.MatchError: Cat(freddy,3) (of class Cat)
MatchError
Это не очень хорошая вещь, поэтому давайте использовать сопоставление с образцом:
val conditions = Seq(mycat, yourcat) map {
case OldCatName(oldie) => s"$oldie is old"
case Cat(name, age) => s"At age $age $name is not old"
}
// conditions: Seq[String] = List(At age 3 freddy is not old, betty is old)
Еще одна магия, связанная с unapply
метод для ::
является то, что некоторый синтаксический сахар позволяет val ::(head, tail) = ...
быть написанным val head :: tail = ...
вместо.