Использование Option и Future
Монадическое объединение в цепочку
«Использование Option и Future» — бесплатный урок Scala for Backend Engineering & Functional Programming на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Scala for Backend Engineering & Functional Programming, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.
Не только коллекции
For-выражения не ограничены списками. Можно использовать любой тип, предоставляющий map и flatMap, включая Option, Either, Try и Future. Это называется монадной цепочкой.
object Main {
def main(args: Array[String]): Unit = {
val sum = for {
a <- Some(10)
b <- Some(20)
} yield a + b
println(sum)
}
}Option прерывает вычисление при None
С Option for-выражение создаёт результат только в том случае, если каждый генератор содержит Some. Если хотя бы один содержит None, весь результат становится None, а последующие шаги пропускаются.
object Main {
def main(args: Array[String]): Unit = {
val ok = for {
a <- Some(2)
b <- Some(3)
} yield a * b
val missing = for {
a <- Some(2)
b <- None: Option[Int]
} yield a * b
println(ok)
println(missing)
}
}Цепочка необязательных поисковых операций
Распространённый шаблон: поиск ключей в Map, где каждая операция поиска возвращает Option. For-выражение выдаёт значение только при успешном выполнении всех поисковых операций.
object Main {
def main(args: Array[String]): Unit = {
val prices = Map("apple" -> 3, "bread" -> 5)
val total = for {
a <- prices.get("apple")
b <- prices.get("bread")
} yield a + b
println(total)
val missing = for {
a <- prices.get("apple")
c <- prices.get("milk")
} yield a + c
println(missing)
}
}Почему это лучше вложенных if-else
Без for-выражений пришлось бы вкладывать сопоставления с образцом или проверки на null. Монадная форма превращает эту пирамиду в чистую линейную последовательность.
object Main {
def parse(s: String): Option[Int] = s.toIntOption
def main(args: Array[String]): Unit = {
val result = for {
x <- parse("40")
y <- parse("2")
} yield x + y
println(result)
}
}Защитные условия работают и с Option
Защитное условие внутри for-выражения с Option превращает значение в None, если условие не выполнено, выполняя роль встроенной проверки.
object Main {
def main(args: Array[String]): Unit = {
def validate(age: Int): Option[Int] =
for {
a <- Some(age)
if a >= 18
} yield a
println(validate(25))
println(validate(15))
}
}Знакомство с Future
Future представляет значение, которое станет доступно позднее. Как и Option, он содержит map и flatMap, поэтому for-выражения выстраивают асинхронные шаги в последовательность.
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration._
object Main {
def main(args: Array[String]): Unit = {
val f = for {
a <- Future(10)
b <- Future(20)
} yield a + b
println(Await.result(f, 2.seconds))
}
}Здесь Future выполняются последовательно
Когда один генератор зависит от другого внутри for-выражения, Future выполняются последовательно, поскольку каждый flatMap ожидает результат предыдущего.
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration._
object Main {
def main(args: Array[String]): Unit = {
val f = for {
a <- Future(5)
b <- Future(a * 2)
} yield a + b
println(Await.result(f, 2.seconds))
}
}Параллельный запуск Future
Чтобы запустить Future параллельно, создайте их до for-выражения, а затем объедините. Независимые операции выполняются одновременно, не ожидая друг друга.
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration._
object Main {
def main(args: Array[String]): Unit = {
val fa = Future(100)
val fb = Future(200)
val combined = for {
a <- fa
b <- fb
} yield a + b
println(Await.result(combined, 2.seconds))
}
}Either для более подробных ошибок
Either[E, A] содержит значение ошибки при неудаче. В for-выражении первый Left прерывает вычисление и возвращается как результат.
object Main {
def parse(s: String): Either[String, Int] =
s.toIntOption.toRight(s"not a number: $s")
def main(args: Array[String]): Unit = {
val good = for { a <- parse("3"); b <- parse("4") } yield a + b
val bad = for { a <- parse("3"); b <- parse("x") } yield a + b
println(good)
println(bad)
}
}Одна форма для разных типов
Обратите внимание: for-выражение выглядит одинаково при использовании Option, Either, Try или Future. Различается только поведение при неудаче. В этом и заключается сила монадной цепочки.
import scala.util.Try
object Main {
def main(args: Array[String]): Unit = {
val result = for {
a <- Try("21".toInt)
b <- Try("2".toInt)
} yield a * b
println(result)
}
}Смешивать типы нельзя
Все генераторы одного for-выражения должны принадлежать одной и той же монаде. Нельзя напрямую смешивать генератор Option с генератором Future; сначала преобразуйте один из них, например с помощью Future.fromTry, или оберните Option.
object Main {
def main(args: Array[String]): Unit = {
// Convert the Option to keep types consistent
val maybe: Option[Int] = Some(7)
val result = for {
a <- maybe
b <- maybe.map(_ + 1)
} yield a + b
println(result)
}
}Быстрая проверка
Что происходит в for-выражении с Option, если один из генераторов выдаёт None?
Итоги
Вы узнали о монадной цепочке с for-выражениями:
- Она работает с любым типом, содержащим
mapиflatMap Optionпрерывает вычисление приNoneEitherиTryпрерывают вычисление при ошибке, сохраняя сведения об ошибкеFutureвыстраивает асинхронные шаги в последовательность; для параллельного выполнения запускайте их заранее- Все генераторы должны использовать один и тот же тип монады
Часто задаваемые вопросы
Урок «Использование Option и Future» бесплатный?
Да — полный текст урока «Использование Option и Future» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Scala for Backend Engineering & Functional Programming, подпишись на CoddyKit PRO. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.
Чему я научусь в уроке «Использование Option и Future»?
Монадическое объединение в цепочку Ты практикуешь Scala for Backend Engineering & Functional Programming с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Scala for Backend Engineering & Functional Programming?
Предыдущий опыт не требуется. Scala for Backend Engineering & Functional Programming на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Использование Option и Future»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Scala for Backend Engineering & Functional Programming?
Да. Каждый урок Scala for Backend Engineering & Functional Programming включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Десахаризация for
- Фильтрация с помощью if
- Несколько генераторов
- Использование Option и Future