0Pricing
Scala for Backend Engineering & Functional Programming · Урок

Композиция Either

map и flatMap

«Композиция Either» — бесплатный урок Scala for Backend Engineering & Functional Programming на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Scala for Backend Engineering & Functional Programming, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.

Композиция вычислений

В реальных программах несколько этапов могут завершаться ошибкой: разбор, проверка, поиск, вычисление. Either позволяет объединить их так, чтобы первая ошибка немедленно прерывала всю цепочку.

Для этого используются map, flatMap и конструкции for.

flatMap выстраивает последовательность Either

flatMap применяет функцию, которая сама возвращает Either. Если получатель содержит Left, функция не вызывается, а Left передаётся дальше.

object Main {
  def positive(n: Int): Either[String, Int] =
    if (n > 0) Right(n) else Left("not positive")

  def main(args: Array[String]): Unit = {
    val r = Right(5).flatMap(positive)
    val l = Right(-1).flatMap(positive)
    println(r)
    println(l)
  }
}

Прерывание цепочки

При объединении нескольких вызовов flatMap первый Left останавливает всё выполнение. Последующие этапы пропускаются, и эта ошибка становится итоговым результатом.

object Main {
  def step(label: String, n: Int): Either[String, Int] = {
    println(s"running $label")
    Right(n + 1)
  }

  def main(args: Array[String]): Unit = {
    val result = Left("early error").asInstanceOf[Either[String, Int]]
      .flatMap(n => step("A", n))
      .flatMap(n => step("B", n))
    println(result)
  }
}

Конструкция for для Either

Поскольку Either ориентирован вправо, его можно использовать в конструкции for. Каждый оператор <- извлекает значение из Right; любой Left останавливает конструкцию и становится её результатом.

object Main {
  def parse(s: String): Either[String, Int] =
    s.toIntOption.toRight(s"bad: $s")

  def main(args: Array[String]): Unit = {
    val sum = for {
      a <- parse("3")
      b <- parse("4")
    } yield a + b
    println(sum)
  }
}

Конструкция for с ошибкой

Если любой этап конструкции выдаёт Left, всё выражение становится этим Left, а последующие этапы не выполняются.

object Main {
  def parse(s: String): Either[String, Int] =
    s.toIntOption.toRight(s"bad: $s")

  def main(args: Array[String]): Unit = {
    val sum = for {
      a <- parse("3")
      b <- parse("oops")
      c <- parse("5")
    } yield a + b + c
    println(sum)
  }
}

Цепочки проверок

Реалистичная цепочка обработки: разобрать строку, затем проверить бизнес-правило и преобразовать результат. Каждый этап возвращает Either.

object Main {
  def parse(s: String): Either[String, Int] = s.toIntOption.toRight("not a number")
  def checkRange(n: Int): Either[String, Int] =
    if (n >= 1 && n <= 100) Right(n) else Left("out of range")

  def process(s: String): Either[String, Int] =
    parse(s).flatMap(checkRange).map(_ * 10)

  def main(args: Array[String]): Unit = {
    println(process("7"))
    println(process("500"))
    println(process("x"))
  }
}

map и flatMap

Используйте map, когда функция возвращает обычное значение. Используйте flatMap, когда она возвращает другой Either, чтобы избежать вложенного Either[String, Either[String, Int]].

@main def run(): Unit = {
  val withMap: Either[String, Int] = Right(2).map(_ + 1)
  val nested: Either[String, Either[String, Int]] = Right(2).map(n => Right(n + 1))
  val flat: Either[String, Int] = Right(2).flatMap(n => Right(n + 1))
  println(withMap)
  println(nested)
  println(flat)
}

Объединение независимых значений

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

object Main {
  def parse(s: String): Either[String, Int] = s.toIntOption.toRight(s"bad: $s")

  def main(args: Array[String]): Unit = {
    val combined = for {
      x <- parse("10")
      y <- parse("20")
      z <- parse("30")
    } yield List(x, y, z).sum
    println(combined)
  }
}

Преобразование ошибок в стиле leftMap

В стандартной библиотеке Either нет leftMap, но ошибку можно преобразовать с помощью swap.map(...).swap или сопоставления внутри fold. Это помогает сохранять согласованные типы ошибок во всей цепочке обработки.

@main def run(): Unit = {
  val e: Either[String, Int] = Left("low-level error")
  val mapped = e.swap.map(msg => s"context: $msg").swap
  println(mapped)
}

Собираем всё вместе

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

object Main {
  def parse(s: String): Either[String, Int] = s.toIntOption.toRight(s"bad number: $s")
  def divide(a: Int, b: Int): Either[String, Int] =
    if (b == 0) Left("division by zero") else Right(a / b)

  def calc(x: String, y: String): Either[String, Int] =
    for {
      a <- parse(x)
      b <- parse(y)
      r <- divide(a, b)
    } yield r

  def main(args: Array[String]): Unit = {
    println(calc("20", "4"))
    println(calc("20", "0"))
    println(calc("x", "4"))
  }
}

Почему это важно

Композиция Either даёт Вам программирование, ориентированное на железнодорожные пути: успешный сценарий проходит через Right, а любая ошибка сворачивает на путь Left и обходит остальные этапы. Никаких исключений, проверок на null — только значения.

Быстрая проверка

Проверьте, насколько хорошо Вы понимаете композицию Either.

Итоги

Вы научились создавать композиции с Either:

  • flatMap выстраивает последовательность этапов, способных завершиться ошибкой, и прерывает её на первом Left.
  • Конструкции for делают многоэтапные цепочки обработки удобными для чтения.
  • Используйте map для обычных результатов, а flatMap — для функций, возвращающих Either.
  • Преобразуйте ошибки с помощью swap.map(...).swap.

Часто задаваемые вопросы

Урок «Композиция Either» бесплатный?

Да — полный текст урока «Композиция Either» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Scala for Backend Engineering & Functional Programming, подпишись на CoddyKit PRO. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.

Чему я научусь в уроке «Композиция Either»?

map и flatMap Ты практикуешь Scala for Backend Engineering & Functional Programming с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Scala for Backend Engineering & Functional Programming?

Предыдущий опыт не требуется. Scala for Backend Engineering & Functional Programming на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Композиция Either»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Scala for Backend Engineering & Functional Programming?

Да. Каждый урок Scala for Backend Engineering & Functional Programming включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Either для ошибок
  2. Try, Success, Failure
  3. Композиция Either
  4. Преобразование между типами
← Назад к Scala for Backend Engineering & Functional Programming