16-й президент США. Авраам Линкольн однажды сказал: «Поскольку наше дело новое, мы должны думать и действовать заново». В разработке программного обеспечения вещи, вероятно, не так драматичны, как гражданские войны и отмена рабства, но у нас есть интересные логические концепции относительно «случая». В Java оператор case предусматривает некоторое ограниченное условное ветвление. В Scala можно построить очень сложную логику сопоставления с образцом, используя конструкцию case / match, которая не просто приносит новые возможности, но и новый тип мышления для реализации новых возможностей.
Давайте начнем с классического домашнего задания 1-го курса Computer Science: серия Фибоначчи, которая не начинается с 0, 1, а начинается с 1, 1. Таким образом, серия будет выглядеть так: 1, 1, 2, 3, 5, 8 , 13, …каждое число является суммой двух предыдущих,
В Java мы могли бы сделать:
public int fibonacci(int i) { if (i < 0) return 0; switch(i) { case 0: return 1; case 1: return 1; default: return fibonacci(i-1) + fibonacci(i - 2); } }
Все прямо вперед. Если 0
он передается, он считается первым элементом в серии, поэтому 1
должен быть возвращен. Примечание: чтобы добавить еще немного остроты на вечеринку и сделать вещи немного более интересными, я добавил немного логики, чтобы возвращать, 0
если отрицательное число передается в наш метод Фибоначчи.
В Scala для достижения того же поведения мы бы сделали:
def fibonacci(in: Int): Int = { in match { case n if n <= 0 => 0 case 0 | 1 => 1 case n => fibonacci(n - 1) + fibonacci(n- 2) } }
Ключевые моменты:
- Тип возврата рекурсивного метода Фибоначчи —
Int
. Рекурсивные методы должны явно указывать тип возвращаемого значения (см .: Одерский — Программирование в Scala — Глава 2). - Можно проверить наличие нескольких значений в одной строке, используя
|
обозначения. Я делаю это, чтобы вернуть 1 для 0 и 1 в строке 4 примера . - Нет необходимости в нескольких
return
утверждениях. В Java вы должны использовать несколькоreturn
операторов или несколькоbreak
операторов. - Сопоставление с образцом — это выражение, которое всегда что-то возвращает.
- В этом примере я использую охрану для проверки на отрицательное число, и если число отрицательное, возвращается ноль.
- В Scala также можно проверять разные типы. Также возможно использовать символ подстановки
_
. Мы не использовали ни в Фибоначчи, но просто чтобы проиллюстрировать эти функции …def multitypes(in: Any): String = in match { case i:Int => "You are an int!" case "Alex" => "You must be Alex" case s:String => "I don't know who you are but I know you are a String" case _ => "I haven't a clue who you are" }
Сопоставление с образцом может быть использовано с Scala Maps для полезного эффекта. Предположим, у нас есть Карта, чтобы отразить, кто, по нашему мнению, должен играть в каждой позиции линии Lions для австралийской серии Lions . Ключи карты будут позицией в задней линии, а соответствующее значение будет игроком, который, по нашему мнению, должен играть там. Для представления игрока в регби мы используем класс case . Теперь, вы, Java-главы, представьте, что класс case является неизменяемым POJO, написанным в чрезвычайно сжатой форме — они тоже могут быть изменяемыми, но пока думаем, что они неизменные.
case class RugbyPlayer(name: String, country: String); val robKearney = RugbyPlayer("Rob Kearney", "Ireland"); val georgeNorth = RugbyPlayer("George North", "Wales"); val brianODriscol = RugbyPlayer("Brian O'Driscol", "Ireland"); val jonnySexton = RugbyPlayer("Jonny Sexton", "Ireland"); val benYoungs = RugbyPlayer("Ben Youngs", "England"); // build a map val lionsPlayers = Map("FullBack" -> robKearney, "RightWing" -> georgeNorth, "OutsideCentre" -> brianODriscol, "Outhalf" -> jonnySexton, "Scrumhalf" -> benYoungs); // Note: Unlike Java HashMaps Scala Maps can return nulls. This achieved by returing // an Option which can either be Some or None. // So, if we ask for something that exists in the Map like below println(lionsPlayers.get("Outhalf")); // Outputs: Some(RugbyPlayer(Jonny Sexton,Ireland)) // If we ask for something that is not in the Map yet like below println(lionsPlayers.get("InsideCentre")); // Outputs: None
В этом примере у нас есть игроки на каждую позицию, кроме внутреннего центра — о чем мы не можем определиться . Карты Scala позволяют хранить значения NULL в качестве значений. Теперь в нашем случае мы фактически не храним нулевое значение для внутреннего центра . Таким образом, вместо того, чтобы возвращать значение null для внутреннего центра (как, если бы мы использовали Java HashMap), None
возвращается тип .
Для других позиций в задней строке у нас есть совпадающие значения, и Some
возвращается тип, который оборачивается вокруг соответствующего RugbyPlayer. (Примечание: оба Some
и None простираются от Option
).
Мы можем написать функцию, образец которой соответствует на возвращаемое значение из HashMap и возвращает нам что-то более удобное для пользователя.
def show(x: Option[RugbyPlayer]) = x match { case Some(rugbyPlayerExt) => rugbyPlayerExt.name // If a rugby player is matched return its name case None => "Not decided yet ?" // } println(show(lionsPlayers.get("Outhalf"))) // outputs: Jonny Sexton println(show(lionsPlayers.get("InsideCentre"))) // Outputs: Not decided yet
Этот пример иллюстрирует не только сопоставление с образцом, но и другую концепцию, известную как извлечение . Игрок в регби при совпадении извлекается и присваивается rugbyPlayerExt
. Затем мы можем вернуть значение имени игрока в регби, получив его от rugbyPlayerExt
. На самом деле, мы также можем добавить охрану и изменить некоторую логику. Предположим, у нас был предвзятый журналист ( Стивен Джонс ), который не хотел, чтобы в команде были ирландские игроки. Он мог реализовать свою собственную предвзятую функцию, чтобы проверить ирландских игроков
def biasedShow(x: Option[RugbyPlayer]) = x match { case Some(rugbyPlayerExt) if rugbyPlayerExt.country == "Ireland" => rugbyPlayerExt.name + ", don't pick him." case Some(rugbyPlayerExt) => rugbyPlayerExt.name case None => "Not decided yet ?" } println(biasedShow(lionsPlayers.get("Outhalf"))) // Outputs Jonny... don't pick him println(biasedShow(lionsPlayers.get("Scrumhalf"))) // Outputs Ben Youngs
Коллекции соответствия шаблонов
Scala также предоставляет некоторые мощные функции сопоставления с образцами для коллекций. Вот тривиальный экзамен для получения длины списка.
def length[A](list : List[A]) : Int = list match { case _ :: tail => 1 + length(tail) case Nil => 0 }
И предположим, что мы хотим разобрать аргументы из кортежа …
def parseArgument(arg : String, value: Any) = (arg, value) match { case ("-l", lang) => setLanguage(lang) case ("-o" | "--optim", n : Int) if ((0 < n) && (n <= 3)) => setOptimizationLevel(n) case ("-h" | "--help", null) => displayHelp() case bad => badArgument(bad) }
Функции с одним параметром
Рассмотрим список чисел от 1 до 10. Метод фильтра принимает функцию с одним параметром, которая возвращает
true
илиfalse
. Функция с одним параметром может применяться для каждого элемента в списке и будет возвращатьсяtrue
илиfalse
для каждого элемента. Возвращаемые элементыtrue
будут отфильтрованы; возвращаемые элементыfalse
будут отфильтрованы из результирующего списка.scala> val myList = List(1,2,3,4,5,6,7,8,9,10) myList: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) scala> myList.filter(x => x % 2 ==1) res13: List[Int] = List(1, 3, 5, 7, 9)
Теперь сейчас, слушай и запомни это.
Шаблон может быть передан любому методу, который принимает функцию с одним параметром . Вместо передачи функции с одним параметром, которая всегда возвращала истину или ложь, мы могли бы использовать шаблон, который всегда возвращает истину или ложь.
scala> myList.filter { | case i: Int => i % 2 == 1 // odd number will return false | case _ => false // anything else will return false | } res14: List[Int] = List(1, 3, 5, 7, 9)
Использовать это позже?
Scala компилирует шаблоны в
PartialFunction
. Это означает, что выражения шаблона Scala не только могут быть переданы другим функциям, но также могут быть сохранены для дальнейшего использования.
scala> val patternToUseLater = : PartialFunction[String, String] = { | case "Dublin" => "Ireland" | case _ => "Unknown" }
Этот пример говорит о том,
patternToUseLater
что это частичная функция, которая принимает строку и возвращает строку. Последний элемент состояния в функции возвращается по умолчанию, и поскольку выражение case является частичной функцией, оно будет возвращено как частичная функция и назначено,pattenrToUseLater
что, конечно, может использовать ее позже.Наконец,
Джонни Секстон — феноменальный игрок в регби, и стыдно слышать, что он покидает Ленстер. Очевидно, что из-за напряженного графика Секстона мы не можем быть уверены, что Джонни читает этот блог, но если он, Джонни, извините, что вы ушли, мы желаем вам всего наилучшего и, надеюсь, еще раз увидимся в Голубом Джерси.