Как сопоставление с образцом 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 = ... вместо.

Другие вопросы по тегам