Scala 3 — Book

Структуры управления

Language

В Scala есть все структуры управления, которые вы найдете в других языках программирования, а также мощные for и match выражения:

  • if/else
  • for циклы и выражения
  • match выражения
  • while циклы
  • try/catch

Эти структуры демонстрируются в следующих примерах.

if/else

В Scala структура управления if/else похожа на аналогичные структуры в других языках.

if (x < 0) {
  println("negative")
} else if (x == 0) {
  println("zero")
} else {
  println("positive")
}
if x < 0 then
  println("negative")
else if x == 0 then
  println("zero")
else
  println("positive")

Обратите внимание, что это действительно выражение, а не утверждение. Это означает, что оно возвращает значение, поэтому вы можете присвоить результат переменной:

val x = if (a < b) { a } else { b }
val x = if a < b then a else b

Как вы увидите в этой книге, все управляющие структуры Scala могут использоваться как выражения.

Выражение возвращает результат, а утверждение — нет. Утверждения обычно используются для их побочных эффектов, таких как использование println для печати на консоли.

for циклы и выражения

Ключевое слово for используется для создания цикла for. В этом примере показано, как напечатать каждый элемент в List:

val ints = List(1, 2, 3, 4, 5)

for (i <- ints) println(i)

Код i <- ints называется генератором, а код, следующий за закрывающими скобками генератора, является телом цикла.

val ints = List(1, 2, 3, 4, 5)

for i <- ints do println(i)

Код i <- ints называется генератором, а код, следующий за ключевым словом do, является телом цикла.

Guards

Вы также можете использовать одно или несколько if выражений внутри цикла for. Их называют ограничители (guards). В этом примере выводятся все числа ints, большие 2:

for (i <- ints if i > 2)
  println(i)
for
  i <- ints
  if i > 2
do
  println(i)

Вы можете использовать несколько генераторов и стражников. Этот цикл перебирает числа от 1 до 3, и для каждого числа также перебирает символы от a до c. Однако у него также есть два стражника, поэтому оператор печати вызывается только тогда, когда i имеет значение 2 и j является символом b:

for {
  i <- 1 to 3
  j <- 'a' to 'c'
  if i == 2
  if j == 'b'
} {
  println(s"i = $i, j = $j")   // печатает: "i = 2, j = b"
}
for
  i <- 1 to 3
  j <- 'a' to 'c'
  if i == 2
  if j == 'b'
do
  println(s"i = $i, j = $j")   // печатает: "i = 2, j = b"

Выражения for

Ключевое слово for содержит в себе еще большую силу: когда вы используете ключевое слово yield вместо do, то создаете выражения for, которые используются для вычислений и получения результатов.

Несколько примеров демонстрируют это. Используя тот же список ints, что и в предыдущем примере, этот код создает новый список, в котором значение каждого элемента в новом списке в два раза превышает значение элементов в исходном:

scala> val doubles = for (i <- ints) yield i * 2
val doubles: List[Int] = List(2, 4, 6, 8, 10)
scala> val doubles = for i <- ints yield i * 2
val doubles: List[Int] = List(2, 4, 6, 8, 10)

Синтаксис структуры управления Scala является гибким, и это for выражение может быть записано несколькими другими способами, в зависимости от ваших предпочтений:

val doubles = for (i <- ints) yield i * 2
val doubles = for (i <- ints) yield (i * 2)
val doubles = for { i <- ints } yield (i * 2)
val doubles = for i <- ints yield i * 2     // стиль показан выше
val doubles = for (i <- ints) yield i * 2
val doubles = for (i <- ints) yield (i * 2)
val doubles = for { i <- ints } yield (i * 2)

В этом примере показано, как сделать первый символ в каждой строке списка заглавными:

val names = List("chris", "ed", "maurice")
val capNames = for (name <- names) yield name.capitalize
val names = List("chris", "ed", "maurice")
val capNames = for name <- names yield name.capitalize

Наконец, нижеследующее выражение for перебирает список строк и возвращает длину каждой строки, но только если эта длина больше 4:

val fruits = List("apple", "banana", "lime", "orange")

val fruitLengths =
  for (f <- fruits if f.length > 4) yield f.length

// fruitLengths: List[Int] = List(5, 6, 6)
val fruits = List("apple", "banana", "lime", "orange")

val fruitLengths = for
  f <- fruits
  if f.length > 4
yield
  // здесь можно использовать
  // несколько строк кода
  f.length

// fruitLengths: List[Int] = List(5, 6, 6)

for циклы и выражения более подробно рассматриваются в разделах этой книги “Структуры управления” и в справочной документации.

match выражения

В Scala есть выражение match, которое в своем самом простом использовании похоже на switch оператор Java:

val i = 1

// позже в этом коде ...
i match {
  case 1 => println("one")
  case 2 => println("two")
  case _ => println("other")
}
val i = 1

// позже в этом коде ...
i match
  case 1 => println("one")
  case 2 => println("two")
  case _ => println("other")

Однако match на самом деле это выражение, означающее, что оно возвращает результат на основе совпадения с шаблоном, который вы можете привязать к переменной:

val result = i match {
  case 1 => "one"
  case 2 => "two"
  case _ => "other"
}
val result = i match
  case 1 => "one"
  case 2 => "two"
  case _ => "other"

match не ограничивается работой только с целочисленными значениями, его можно использовать с любым типом данных:

val p = Person("Fred")

// позже в этом коде ...
p match {
  case Person(name) if name == "Fred" =>
    println(s"$name says, Yubba dubba doo")

  case Person(name) if name == "Bam Bam" =>
    println(s"$name says, Bam bam!")

  case _ => println("Watch the Flintstones!")
}
val p = Person("Fred")

// позже в этом коде ...
p match
  case Person(name) if name == "Fred" =>
    println(s"$name says, Yubba dubba doo")

  case Person(name) if name == "Bam Bam" =>
    println(s"$name says, Bam bam!")

  case _ => println("Watch the Flintstones!")

На самом деле match выражение можно использовать для проверки переменной на множестве различных типов шаблонов. В этом примере показано (а) как использовать match выражение в качестве тела метода и (б) как сопоставить все показанные различные типы:

// getClassAsString - метод, принимающий один параметр любого типа.
def getClassAsString(x: Any): String = x match {
  case s: String => s"'$s' is a String"
  case i: Int => "Int"
  case d: Double => "Double"
  case l: List[_] => "List"
  case _ => "Unknown"
}

// примеры
getClassAsString(1)               // Int
getClassAsString("hello")         // 'hello' is a String
getClassAsString(List(1, 2, 3))   // List

Поскольку метод getClassAsString принимает значение параметра типа Any, его можно разложить по любому шаблону.

// getClassAsString - метод, принимающий один параметр любого типа.
def getClassAsString(x: Matchable): String = x match
  case s: String => s"'$s' is a String"
  case i: Int => "Int"
  case d: Double => "Double"
  case l: List[?] => "List"
  case _ => "Unknown"

// примеры
getClassAsString(1)               // Int
getClassAsString("hello")         // 'hello' is a String
getClassAsString(List(1, 2, 3))   // List

Метод getClassAsString принимает в качестве параметра значение типа Matchable, которое может быть любым типом, поддерживающим сопоставление с образцом (некоторые типы не поддерживают сопоставление с образцом, поскольку это может нарушить инкапсуляцию).

Сопоставление с образцом в Scala гораздо шире. Шаблоны могут быть вложены друг в друга, результаты шаблонов могут быть связаны, а сопоставление шаблонов может даже определяться пользователем. Дополнительные сведения см. в примерах сопоставления с образцом в главе “Структуры управления”.

try/catch/finally

Структура управления Scala try/catch/finally позволяет перехватывать исключения. Она похожа на аналогичную структуру в Java, но её синтаксис соответствует match выражениям:

try {
  writeTextToFile(text)
} catch {
  case ioe: IOException => println("Got an IOException.")
  case nfe: NumberFormatException => println("Got a NumberFormatException.")
} finally {
  println("Clean up your resources here.")
}
try
  writeTextToFile(text)
catch
  case ioe: IOException => println("Got an IOException.")
  case nfe: NumberFormatException => println("Got a NumberFormatException.")
finally
  println("Clean up your resources here.")

Циклы while

В Scala также есть конструкция цикла while. Его однострочный синтаксис выглядит так:

while (x >= 0) { x = f(x) }
while x >= 0 do x = f(x)

Scala 3 по-прежнему поддерживает синтаксис Scala 2 для обратной совместимости.

Синтаксис while многострочного цикла выглядит следующим образом:

var x = 1

while (x < 3) {
  println(x)
  x += 1
}
var x = 1

while
  x < 3
do
  println(x)
  x += 1

Пользовательские структуры управления

Благодаря таким функциям, как параметры по имени, инфиксная нотация, плавные интерфейсы, необязательные круглые скобки, методы расширения и функции высшего порядка, вы также можете создавать свой собственный код, который работает так же, как управляющая структура. Вы узнаете об этом больше в разделе “Структуры управления”.

Contributors to this page: