Глава 2: Извлекаем коллекции значений
=========================================

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

К примеру, мы можем определить образец, который представляет только списки из двух элементов,
или образец, который представляет только списки из трёх элементов:

~~~scala
val xs = 3 :: 6 :: 12 :: Nil
xs match {
  case List(a, b) => a * b
  case List(a, b, c) => a + b + c
  case _ => 0
}
~~~

Также мы можем представить списки, размер которых нам заранее неизвестен, с помощью оператора `_*`:

~~~scala
val xs = 3 :: 6 :: 12 :: 24 :: Nil
xs match {
  case List(a, b, _*) => a * b
  case _ => 0
}
~~~

Здесь сопоставление с образцом проходит успешно. В первой `case`-альтернативе происходит связывание 
переменных `a` и `b`, остаток списка отбрасывается. Нам не важно, сколько элементов осталось в списке.

Конечно, экстракторы для таких образцов не могут быть определены с помощью методов из предыдущей главы.
Нам нужно определить экстрактор, который принимает объекты определённого типа и возвращает 
коллекцию значений, длина которой неизвестна на этапе компиляции.

Как раз для этого и предназначен метод `unapplySeq`. Посмотрим на возможные сигнатуры типов:

~~~scala
def unapplySeq(object: S): Option[Seq[T]]
~~~

Он принимает на вход значение типа `S` и возвращает либо `None`, если значение совсем не подходит,
или коллекцию значений некоторого типа `T`, обёрнутую в `Some`.

Пример: извлечение полного имени
---------------------------------------------------------

Давайте поупражняемся с новым типом экстракторов, пусть и на несколько надуманном примере. 
Предположим, что в нашем приложении нам необходимо извлечь имя пользователя.
В английском языке имя может состоять из нескольких имён, например:
"Daniel", "Catherina Johanna", или "Matthew John Michael". Нам бы хотелось уметь
извлекать все части составного имени.

Вот очень простой экстрактор, определённый с помощью метода `unapplySeq`, который 
как раз этим и занимается:

~~~scala
object GivenNames {
  def unapplySeq(name: String): Option[Seq[String]] = {
    val names = name.trim.split(" ")
    if (names.forall(_.isEmpty)) None else Some(names)
  }
}
~~~

Если строка содержит одно или несколько имён, экстрактор извлечёт всю последовательность
имён. Если строка не содержит ни одного имени, экстрактор вернёт `None` и строка не пройдёт 
соответствие с образцом.

Давайте протестируем наш новый экстрактор:

~~~scala
def greetWithFirstName(name: String) = name match {
  case GivenNames(firstName, _*) => "Good morning, " + firstName + "!"
  case _ => "Welcome! Please make sure to fill in your name!"
}
~~~

Этот изящный метод возвращает приветствие для полного имени, извлекая лишь первую часть.
Так `greetWithFirstName("Daniel")` вернёт `"Good morning, Daniel!"`, в то время как,
вызвав `greetWithFirstName("Catherina Johanna")`, мы получим `"Good morning, Catherina!"`.

Совместное применение экстракторов с фиксированным и переменным числом параметров
--------------------------------------------------------------------

Иногда нам хочется извлечь определённый набор значений, число которых известно на этапе компиляции,
а также дополнительный набор значений, число которых заранее неизвестно, их может и не быть вовсе. 

Предположим, что нам нужно извлечь полное имя. Оно содержит имя человека (возможно составное), а 
также и фамилию, к примеру: `"John Doe"` или `"Catherina Johanna Peterson"`. Мы хотим связать 
фамилию с первой переменной образца, основное имя — со второй переменной и с третьей 
коллекцию из оставшихся имён. 

Для этого воспользуемся другим вариантом метода `unapplySeq`:

~~~scala
def unapplySeq(object: S): Option[(T1,  .., Tn-1, Seq[T])]
~~~

Как видно из сигнатуры, метод `unapplySeq` также может возвращать кортеж значений, в котором 
последний элемент должен быть коллекцией типа `Seq`. Этот вариант очень похож на то, что
мы видели в предыдущей главе для метода `unapply`.

Посмотрим на определение экстрактора:

~~~scala
object Names {
  def unapplySeq(name: String): Option[(String, String, Seq[String])] = {
    val names = name.trim.split(" ")
    if (names.size < 2) None
    else Some((names.last, names.head, names.drop(1).dropRight(1)))
  }
}
~~~

Присмотритесь повнимательней к типу возвращаемого значения. Обратите внимание на то, как мы построили кортеж 
в конструкторе `Some`. Мы передали в него кортеж `Tuple3`, построенный с помощью специального синтаксиса для кортежей.
Мы просто заключили три элемента &mdash; фамилию, имя и список дополнительных имён в круглые скобки и разделили их запятыми
(компилятор Scala передаст значения в конструктор Tuple3 за нас).

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

Воспользуемся нашим экстрактором для приветствия пользователя:

~~~scala
def greet(fullName: String) = fullName match {
  case Names(lastName, firstName, _*) => "Good morning, " + firstName + " " + lastName + "!"
  case _ => "Welcome! Please make sure to fill in your name!"
}
~~~

Поэкспериментируйте с этим примером в интерпретаторе или в интерактивной странице IDE (worksheet).

Итоги
---------------------------------------------

В этой статье мы узнали, как определяются экстракторы, которые могут извлекать коллекции значений. 
Экстракторы &mdash; очень мощная возможность языка. 
С их помощью мы можем существенно расширить возможности стандартных образцов.

К концу этой серии статей мы ещё вернёмся к экстракторам. В следующей части мы узнаем о всевозможных вариантах
применения сопоставления с образцом. Пока мы увидели лишь малую часть. 

*Обновление, 24.01.2013: Я поправил пример для экстрактора `GivenNames`, спасибо Christophe Bliard за то, что
указал на ошибку*. 


--------------------------------------------------------------

* <= [Глава 1: Экстракторы](https://github.com/anton-k/ru-neophyte-guide-to-scala/blob/master/src/p01-extractors.md)

* => [Глава 3: Образцы повсюду](https://github.com/anton-k/ru-neophyte-guide-to-scala/blob/master/src/p03-patterns-everywhere.md)

* [Содержание](https://github.com/anton-k/ru-neophyte-guide-to-scala#%D0%9F%D1%83%D1%82%D0%B5%D0%B2%D0%BE%D0%B4%D0%B8%D1%82%D0%B5%D0%BB%D1%8C-%D0%BD%D0%B5%D0%BE%D1%84%D0%B8%D1%82%D0%B0-%D0%BF%D0%BE-scala)