Глава 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`, построенный с помощью специального синтаксиса для кортежей. Мы просто заключили три элемента — фамилию, имя и список дополнительных имён в круглые скобки и разделили их запятыми (компилятор 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). Итоги --------------------------------------------- В этой статье мы узнали, как определяются экстракторы, которые могут извлекать коллекции значений. Экстракторы — очень мощная возможность языка. С их помощью мы можем существенно расширить возможности стандартных образцов. К концу этой серии статей мы ещё вернёмся к экстракторам. В следующей части мы узнаем о всевозможных вариантах применения сопоставления с образцом. Пока мы увидели лишь малую часть. *Обновление, 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)