Предисловие
Это третья часть руководств для начинающих программистов, попадающих в Scala. Другие посты находятся в этом блоге, и вы можете получить ссылки на эти и другие ресурсы на странице ссылок курса по компьютерной лингвистике, для которого я их создаю. Кроме того, вы можете найти эту и другие серии учебников на странице JCG Java Tutorials .
Conditionals
Переменные приходят и переменные уходят, и они принимают разные значения в зависимости от ввода. Как правило, нам необходимо применять различные способы поведения, обусловленные этими ценностями. Например, давайте смоделируем тендер в баре в Остине, который должен убедиться, что он не дает алкоголь лицам младше 21 года.
1
2
3
4
5
6
7
8
|
scala> def serveBeer (customerAge : Int) = if (customerAge > = 21 ) println( "beer" ) else println( "water" ) serveBeer : (customerAge : Int)Unit scala> serveBeer( 23 ) beer scala> serveBeer( 19 ) water |
То, что мы сделали здесь, — это стандартное использование условных выражений для создания того или иного действия — в данном случае просто печатается одно или другое сообщение. Выражение в if (…) является логическим значением, истинным или ложным . Вы можете увидеть это, просто выполнив неравенство напрямую:
1
2
|
scala> 19 > = 21 res 7 : Boolean = false |
И эти выражения могут быть объединены в соответствии со стандартными правилами для соединения и дизъюнкции логических. Конъюнкция обозначается знаком &&, а дизъюнкция — || ,
1
2
3
4
5
|
scala> 19 > = 21 || 5 > 2 res 8 : Boolean = true scala> 19 > = 21 && 5 > 2 res 9 : Boolean = false |
Чтобы проверить равенство, используйте == .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
scala> 42 == 42 res 10 : Boolean = true scala> "the" == "the" res 11 : Boolean = true scala> 3.14 == 6.28 res 12 : Boolean = false scala> 2 * 3.14 == 6.28 res 13 : Boolean = true scala> "there" == "the" + "re" res 14 : Boolean = true |
Оператор равенства == отличается от оператора присваивания = , и вы получите ошибку, если попытаетесь использовать = для тестов на равенство.
01
02
03
04
05
06
07
08
09
10
11
12
|
scala> 5 = 5 <console> : 1 : error : ';' expected but '=' found. 5 = 5 ^ scala> x = 5 <console> : 10 : error : not found : value x val synthvar$ 0 = x ^ <console> : 7 : error : not found : value x x = 5 ^ |
Первый пример совершенно плох, потому что мы не можем надеяться присвоить значение такой константе, как 5. В последнем примере ошибка жалуется на отсутствие значения x . Это потому, что это допустимая конструкция, предполагая, что переменная var была определена ранее.
1
2
3
4
5
|
scala> var x = 0 x : Int = 0 scala> x = 5 x : Int = 5 |
Напомним, что переменным var можно присвоить им новое значение. Тем не менее, на самом деле нет необходимости использовать vars большую часть времени, и есть много преимуществ при использовании vals . Я буду помогать вам думать в этих терминах, пока мы идем вместе. А пока попробуйте игнорировать тот факт, что в языке существуют переменные!
Вернуться к условию. Во-первых, вот несколько операторов сравнения:
х! = у (х не равно у)
х> у (х больше, чем у)
х <у (х меньше у)
x> = y (x равен y или больше y)
x <= y (x равен y или меньше y)
Эти операторы работают с любым типом, имеющим естественный порядок, включая строки.
1
2
3
4
5
6
7
8
|
scala> "armadillo" < "bear" res 25 : Boolean = true scala> "armadillo" < "Bear" res 26 : Boolean = false scala> "Armadillo" < "Bear" res 27 : Boolean = true |
Понятно, что это не обычный алфавитный порядок, к которому вы привыкли. Вместо этого он основан на кодировке символов ASCII.
Очень красивая и полезная вещь об условных выражениях в Scala заключается в том, что они возвращают значение. Таким образом, следующее является допустимым способом установки значений переменных x и y .
1
2
3
4
5
|
scala> val x = if ( true ) 1 else 0 x : Int = 1 scala> val y = if ( false ) 1 else 0 y : Int = 0 |
Здесь не так впечатляет, но давайте вернемся к бармену, и вместо функции serveBeer, печатающей строку, мы можем заставить его вернуть строку, представляющую напиток, «пиво» в случае 21+ лет и «воду» в противном случае ,
1
2
3
4
5
6
7
8
|
scala> def serveBeer (customerAge : Int) = if (customerAge > = 21 ) "beer" else "water" serveBeer : (customerAge : Int)java.lang.String scala> serveBeer( 42 ) res 21 : java.lang.String = beer scala> serveBeer( 20 ) res 22 : java.lang.String = water |
Обратите внимание, как первая функция serveBeer вернула Unit, а эта возвращает String. Единица означает, что никакое значение не возвращается — как правило, это не рекомендуется по причинам, о которых мы здесь не говорим. Независимо от этого, общая схема условного присваивания, показанная выше, является тем, что вы будете часто использовать.
У условных выражений также может быть больше, чем просто одно и то же . Например, скажем, что бармен просто подает напитки, соответствующие его возрасту, и что 21+ получают пиво, подростки получают газировку, а маленькие дети — сок.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
scala> def serveDrink (customerAge : Int) = { | if (customerAge > = 21 ) "beer" | else if (customerAge > = 13 ) "soda" | else "juice" | } serveDrink : (customerAge : Int)java.lang.String scala> serveDrink( 42 ) res 35 : java.lang.String = beer scala> serveDrink( 16 ) res 36 : java.lang.String = soda scala> serveDrink( 6 ) res 37 : java.lang.String = juice |
И, конечно, булевы выражения в любом из ifs или else if могут быть комплексными соединениями и дизъюнкциями небольших выражений. Давайте рассмотрим пример, ориентированный на вычислительную лингвистику, который может воспользоваться этим и который мы продолжим строить в последующих уроках.
Все (надеюсь) знают, что такое часть речи. (Если нет, то посмотрите «Грамматический рок» на YouTube.) В компьютерной лингвистике мы склонны использовать очень подробные наборы тегов, которые выходят далеко за рамки «существительного», «глагола», «прилагательного» и так далее. Например, набор тегов из банка Penn Treebank использует NN для существительных единственного числа (таблица), NNS для существительных множественного числа (таблицы), NNP для имени существительного единственного числа (John) и NNPS для имени существительного множественного числа (Vikings).
Вот аннотированное предложение с постагами из первого предложения части «Уолл Стрит джорнал» банка «Пенн Трибэнк» в формате слово / постаг.
Мы вскоре увидим, как обрабатывать их в массовом порядке , но сейчас давайте создадим функцию, которая с помощью условных выражений превращает отдельные теги, такие как «NNP» в «NN» и «JJS» в «JJ». Мы оставим все остальные постаги такими, какие они есть.
Мы начнем с неоптимального решения, а затем уточним его. Первое, что вы можете попробовать — создать регистр для каждого тега полной формы и вывести соответствующий сокращенный тег.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
scala> def shortenPos (tag : String) = { | if (tag == "NN" ) "NN" | else if (tag == "NNS" ) "NN" | else if (tag == "NNP" ) "NN" | else if (tag == "NNPS" ) "NN" | else if (tag == "JJ" ) "JJ" | else if (tag == "JJR" ) "JJ" | else if (tag == "JJS" ) "JJ" | else tag | } shortenPos : (tag : String)java.lang.String scala> shortenPos( "NNP" ) res 47 : java.lang.String = NN scala> shortenPos( "JJS" ) res 48 : java.lang.String = JJ |
Таким образом, он выполняет свою работу, но существует большая избыточность — в частности, возвращаемое значение одинаково для многих случаев. Мы можем использовать дизъюнкты, чтобы справиться с этим.
1
2
3
4
5
|
def shortenPos 2 (tag : String) = { if (tag == "NN" || tag == "NNS" || tag == "NNP" || tag == "NNP" ) "NN" else if (tag == "JJ" || tag == "JJR" || tag == "JJS" ) "JJ" else tag } |
Это логически эквивалентно.
Есть более простой способ сделать это, используя свойства Strings. Здесь метод startWith очень полезен.
1
2
3
4
5
|
scala> "NNP" .startsWith( "NN" ) res 51 : Boolean = true scala> "NNP" .startsWith( "VB" ) res 52 : Boolean = false |
Мы можем использовать это, чтобы упростить функцию сокращения постагов.
1
2
3
4
5
|
def shortenPos 3 (tag : String) = { if (tag.startsWith( "NN" )) "NN" else if (tag.startsWith( "JJ" )) "JJ" else tag } |
Это позволяет очень легко добавить дополнительное условие, которое сворачивает все теги глагола в «VB». (Оставлено как упражнение.)
Последнее замечание об условных присваиваниях: они могут возвращать все, что вам нравится, поэтому, например, следующие действительны. Например, вот (очень) простой (и очень несовершенный) английский родословный, который возвращает корень и суффикс.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
scala> def splitWord (word : String) = { | if (word.endsWith( "ing" )) (word.slice( 0 ,word.length- 3 ), "ing" ) | else if (word.endsWith( "ed" )) (word.slice( 0 ,word.length- 2 ), "ed" ) | else if (word.endsWith( "er" )) (word.slice( 0 ,word.length- 2 ), "er" ) | else if (word.endsWith( "s" )) (word.slice( 0 ,word.length- 1 ), "s" ) | else (word, "" ) | } splitWord : (word : String)(String, java.lang.String) scala> splitWord( "walked" ) res 10 : (String, java.lang.String) = (walk,ed) scala> splitWord( "walking" ) res 11 : (String, java.lang.String) = (walk,ing) scala> splitWord( "booking" ) res 12 : (String, java.lang.String) = (book,ing) scala> splitWord( "baking" ) res 13 : (String, java.lang.String) = (bak,ing) |
Если мы хотим работать со стержнем и суффиксом непосредственно с переменными, мы можем сразу назначить их.
1
2
3
|
scala> val (stem, suffix) = splitWord( "walked" ) stem : String = walk suffix : java.lang.String = ed |
согласование
Scala предоставляет еще один очень мощный способ кодирования условного выполнения, называемый сопоставлением . Они имеют много общего с блоками if-else, но имеют некоторые приятные дополнительные функции. Мы вернемся к сокращающему постагу, начиная с полного списка из тегов и того, что нужно делать в каждом случае, как наша первая попытка с if-else.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
def shortenPosMatch (tag : String) = tag match { case "NN" = > "NN" case "NNS" = > "NN" case "NNP" = > "NN" case "NNPS" = > "NN" case "JJ" = > "JJ" case "JJR" = > "JJ" case "JJS" = > "JJ" case _ = > tag } scala> shortenPosMatch( "JJR" ) res 14 : java.lang.String = JJ |
Обратите внимание, что последний случай с подчеркиванием «_» — это действие по умолчанию , аналогичное «else» в конце блока if-else.
Сравните это с КРП-ELSE функции shortenPos от до того , что было много повторений в своем определении вида «еще если (тег ==«. Совпадение заявления позволяют делать то же самое, но гораздо более сжато и , возможно, гораздо более ясно. Конечно, мы можем сократить это.
1
2
3
4
5
|
def shortenPosMatch 2 (tag : String) = tag match { case "NN" | "NNS" | "NNP" | "NNPS" = > "NN" case "JJ" | "JJR" | "JJS" = > "JJ" case _ = > tag } |
Который немного более читабелен, чем if-else shortenPosMatch2, определенный ранее.
В дополнение к удобочитаемости операторы сопоставления обеспечивают некоторую логическую защиту. Например, если у вас случайно два перекрывающихся случая, вы получите ошибку.
1
2
3
4
5
6
7
8
|
scala> def shortenPosMatchOops (tag : String) = tag match { | case "NN" | "NNS" | "NNP" | "NNPS" = > "NN" | case "JJ" | "JJR" | "JJS" = > "JJ" | case "NN" = > "oops" | case _ = > tag | } <console> : 10 : error : unreachable code case "NN" = > "oops" |
Это очевидный пример, но с более сложными вариантами сопоставления он может уберечь вас от ошибок!
Мы не можем использовать метод startWith так же, как и в случае if-else shorttenPosMatch3 . Тем не менее, мы можем очень хорошо использовать регулярные выражения с операторами match, о которых мы поговорим в следующем уроке.
В действительности операторы match лучше всего проявляют то, что они могут соответствовать гораздо большему, чем просто значения простых переменных, таких как Strings и Ints. Одно из совпадений — проверка типов входных данных для функции, которая может принимать супертип многих типов. Напомним, что Any — это супертип всех типов; если у нас есть следующая функция, которая принимает аргумент с любым типом, мы можем использовать сопоставление, чтобы проверить, что это за тип аргумента, и соответственно изменить поведение.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
scala> def multitypeMatch (x : Any) = x match { | case i : Int = > "an Int: " + i*i | case d : Double = > "a Double: " + d/ 2 | case b : Boolean = > "a Boolean: " + !b | case s : String = > "a String: " + s.length | case (p 1 : String, p 2 : Int) = > "a Tuple[String, Int]: " + p 2 *p 2 + p 1 .length | case (p 1 : Any, p 2 : Any) = > "a Tuple[Any, Any]: (" + p 1 + "," + p 2 + ")" | case _ = > "some other type " + x | } multitypeMatch : (x : Any)java.lang.String scala> multitypeMatch( true ) res 4 : java.lang.String = a Boolean : false scala> multitypeMatch( 3 ) res 5 : java.lang.String = an Int : 9 scala> multitypeMatch(( 1 , 3 )) res 6 : java.lang.String = a Tuple[Any, Any] : ( 1 , 3 ) scala> multitypeMatch(( "hi" , 3 )) res 7 : java.lang.String = a Tuple[String, Int] : 92 |
Так, например, если это Int, мы можем делать такие вещи, как умножение, если это логическое значение, мы можем его отрицать (с!) И так далее. В операторе case мы предоставляем новую переменную с типом соответствия, а затем после стрелки => мы можем использовать эту переменную безопасным для типа образом. Позже мы увидим, как создавать классы (и, в частности, классы случаев), где этот вид функции на основе сопоставления используется регулярно.
Между тем, вот пример простой функции сложения, которая позволяет вводить String или Int для указания своих аргументов. Например, поведение, которое мы желаем, таково:
01
02
03
04
05
06
07
08
09
10
11
|
scala> add( 1 , 3 ) res 4 : Int = 4 scala> add( "one" , 3 ) res 5 : Int = 4 scala> add( 1 , "three" ) res 6 : Int = 4 scala> add( "one" , "three" ) res 7 : Int = 4 |
Давайте предположим, что мы обрабатываем только прописанные версии от 1 до 5, и что любая строка, которую мы не можем обработать (например, «шестерка» и «aardvark»), считается равной 0. Тогда следующие две функции, использующие совпадения, обрабатывают это.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
def convertToInt (x : String) = x match { case "one" = > 1 case "two" = > 2 case "three" = > 3 case "four" = > 4 case "five" = > 5 case _ = > 0 } def add (x : Any, y : Any) = (x,y) match { case (x : Int, y : Int) = > x + y case (x : String, y : Int) = > convertToInt(x) + y case (x : Int, y : String) = > x + convertToInt(y) case (x : String, y : String) = > convertToInt(x) + convertToInt(y) case _ = > 0 } |
Как и блоки if-else, совпадения могут возвращать любой тип, который вам нравится, включая кортежи, списки и многое другое.
Блоки соответствия используются во многих других полезных контекстах, о которых мы поговорим позже. В то же время, стоит также отметить, что сопоставление фактически используется при присвоении переменных. Мы уже видели это с Tuples, но это можно сделать с помощью списков и других типов.
01
02
03
04
05
06
07
08
09
10
11
|
scala> val (x,y) = ( 1 , 2 ) x : Int = 1 y : Int = 2 scala> val colors = List( "blue" , "red" , "yellow" ) colors : List[java.lang.String] = List(blue, red, yellow) scala> val List(color 1 , color 2 , color 3 ) = colors color 1 : java.lang.String = blue color 2 : java.lang.String = red color 3 : java.lang.String = yellow |
Это особенно полезно в случае Args Array, который поступает из командной строки при создании скрипта с Scala. Например, рассмотрим программу, которая запускается следующим образом.
1
2
|
$ scala nextYear.scala John 35 Next year John will be 36 years old. |
Вот как мы можем это сделать. (Сохраните следующие две строки как nextYear.scala и попробуйте.)
1
2
|
val Array(name, age) = args println( "Next year " + name + " will be " + (age.toInt + 1 ) + " years old." ) |
Обратите внимание, что мы должны были сделать age.toInt . Это потому, что сам возраст является String, а не Int.
Условное выполнение с блоками if-else и совпадающими блоками является мощной частью встраивания сложных программ в ваши программы, которые вы будете часто видеть и использовать!
Ссылка: Первые шаги в Scala для начинающих программистов, часть 3 от нашего партнера JCG Джейсона Болдриджа в блоге Bcomposes .
Статьи по Теме :
- Scala Tutorial — Scala REPL, выражения, переменные, основные типы, простые функции, сохранение и запуск программ, комментарии
- Scala Tutorial — кортежи, списки, методы для списков и строк
- Scala Tutorial — итерация, для выражений, yield, map, filter, count
- Scala Tutorial — регулярные выражения, сопоставление
- Scala Tutorial — регулярные выражения, сопоставления и замены с помощью API scala.util.matching
- Учебник по Scala — Карты, Наборы, groupBy, Параметры, Flatten, FlatMap
- Scala Tutorial — scala.io.Source, доступ к файлам, flatMap, изменяемые Карты
- Scala Tutorial — объекты, классы, наследование, черты, списки с несколькими связанными типами, применение
- Scala Tutorial — скриптинг, компиляция, основные методы, возвращаемые значения функций
- Scala Tutorial — SBT, scalabha, пакеты, системы сборки
- Scala Tutorial — блоки кода, стиль кодирования, замыкания, проект документации scala
- Веселье с функцией композиции в Scala
- Как Scala изменил мой взгляд на мой Java-код
- Какие функции Java были исключены в Scala?
- Тестирование с помощью Scala
- Вещи, которые должен знать каждый программист