0Pricing
Kotlin Academy · Урок

DSL Arrow Raise: объединение типизированных ошибок

Используйте контекст Raise в Arrow и raise(), чтобы аккуратно объединять потоки с несколькими ошибками

«DSL Arrow Raise: объединение типизированных ошибок» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.

Ограничения цепочек Either

Цепочка Either с помощью flatMap приводит к глубоко вложенным лямбдам при наличии нескольких последовательных операций. DSL Raise Arrow (появившийся в Arrow 1.2) предлагает более чистый синтаксис в стиле сопрограмм с использованием контекстных получателей Kotlin или функций-расширений.

Что такое Raise<E>

Raise — это контекст, который может поднять (завершить вычисление досрочно) ошибку типа E. Функция, которая может завершиться ошибкой, вызывает raise(error), чтобы прервать выполнение и передать ошибку дальше — подобно выбрасыванию исключения, но без исключений.

Построитель either { }

Построитель either { } создаёт Either<E, A> из блока, в области видимости которого находится Raise<E>. Внутри вызовите raise(error) при ошибке или просто верните значение при успехе:

import arrow.core.raise.either
import arrow.core.raise.Raise

fun validateAge(age: Int): Either<String, Int> = either {
    if (age < 0) raise("Age cannot be negative")
    if (age > 150) raise("Age too large")
    age
}

bind() — распаковка Either внутри either{}

Внутри блока either { } вызовите .bind() для любого значения Either, чтобы распаковать его. Если это Left, bind() автоматически поднимает ошибку и досрочно завершает блок:

fun createUser(name: String, age: Int): Either<String, User> = either {
    val validName = validateName(name).bind()  // raises if Left
    val validAge  = validateAge(age).bind()    // raises if Left
    User(validName, validAge)                  // only reached if both succeed
}

ensure() — встроенная проверка условия

ensure(condition) { error } — это сокращённая форма записи if (!condition) raise(error). Она позволяет сделать логику проверки краткой:

fun validateEmail(email: String): Either<String, String> = either {
    ensure(email.contains("@")) { "Email must contain @" }
    ensure(email.length <= 255) { "Email too long" }
    email
}

ensureNotNull() — проверка null с помощью Raise

ensureNotNull(value) { error } распаковывает значение, допускающее null, или поднимает ошибку, если значение равно null:

fun findUser(id: Long, repo: UserRepo): Either<UserError, User> = either {
    ensureNotNull(repo.findById(id)) { UserError.NotFound(id) }
}

Raise с контекстными получателями (Arrow 1.2+)

Можно объявлять функции, которым непосредственно требуется контекст Raise<E>, без оборачивания в either { }:

context(Raise<String>)
fun requirePositive(n: Int): Int {
    ensure(n > 0) { "Must be positive" }
    return n
}

// Called inside either { }
val result = either { requirePositive(-1) }  // Left("Must be positive")

Накопление ошибок с помощью zipOrAccumulate

По умолчанию Raise завершает выполнение при первой ошибке. Используйте zipOrAccumulate(), чтобы выполнить несколько проверок и собрать все ошибки в NonEmptyList:

val result: Either<NonEmptyList<String>, User> = either {
    zipOrAccumulate(
        { validateName(name).bind() },
        { validateAge(age).bind() }
    ) { validName, validAge -> User(validName, validAge) }
}

recover { } — обработка определённых ошибок

Используйте recover { error -> ... } внутри either { }, чтобы обработать конкретную поднятую ошибку и предоставить резервное значение, продолжив вычисление:

val name: String = either {
    findUser(0L).bind().name
}.recover { error ->
    if (error == UserError.NotFound) "Guest" else raise(error)
}.getOrNull() ?: "Guest"

Raise и цепочки Either: когда что использовать

Используйте either { }.bind() для линейных потоков с несколькими последовательными шагами — такой код читается как императивный, но остаётся чистым. Используйте цепочки flatMap для коротких композиций из двух шагов или при объединении с другими функциональными библиотеками.

Тестирование функций на основе Raise

Проверяйте результат, вызывая функцию внутри either { } и выполняя проверку. Сопоставители Kotest shouldBeLeft() и shouldBeRight() делают утверждения краткими:

import arrow.core.shouldBeLeft
import arrow.core.shouldBeRight

@Test
fun `negative age returns Left`() {
    validateAge(-1).shouldBeLeft()
}

@Test
fun `valid age returns Right`() {
    validateAge(25).shouldBeRight(25)
}

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

Что происходит при вызове .bind() для значения Either.Left внутри блока either { }?

Повторение: DSL Raise Arrow

Основные выводы:

  • either { } создаёт Either из блока, в области видимости которого находится Raise<E>
  • .bind() распаковывает Either; при значении Left завершает выполнение досрочно
  • ensure(condition) { error } и ensureNotNull(value) { error } предназначены для встроенных проверок
  • zipOrAccumulate() собирает несколько ошибок вместо досрочного завершения
  • Код читается как императивный, оставаясь чисто функциональным

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

Урок «DSL Arrow Raise: объединение типизированных ошибок» бесплатный?

Да — полный текст урока «DSL Arrow Raise: объединение типизированных ошибок» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.

Чему я научусь в уроке «DSL Arrow Raise: объединение типизированных ошибок»?

Используйте контекст Raise в Arrow и raise(), чтобы аккуратно объединять потоки с несколькими ошибками Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Kotlin Academy?

Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «DSL Arrow Raise: объединение типизированных ошибок»?

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

Можно ли писать и запускать код в этом уроке Kotlin Academy?

Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

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

  1. Either : типизированная обработка ошибок без исключений
  2. DSL Arrow Raise: объединение типизированных ошибок
  3. Option и Nullable: когда использовать каждый из них
  4. Функциональное моделирование предметной области с базовыми типами Arrow
← Назад к Kotlin Academy