Цепочка Result должна передать успешное значение в следующую операцию, которая сама может вернуть ошибку. Какой метод нужен, чтобы не получить вложенный Result?
Используйте flatMap. Он передаёт успешное значение в следующую операцию, возвращающую Result, и сохраняет плоскую структуру результата: Result<Success, Failure>, а не Result<Result<...>, Failure>.
Если следующая операция не может завершиться ошибкой и возвращает обычное значение, нужен map. flatMap также автоматически сохраняет исходный failure, не вызывая следующую операцию.
Result появился в стандартной библиотеке Swift как явное представление двух исходов операции: успеха или ошибки. Такой подход удобен, когда результат нужно передать между слоями, сохранить, преобразовать или обработать позже.
При последовательной композиции операций возникла практическая проблема: обычное преобразование успешного значения через map не знает, как объединять вложенные результаты. Для этого используется flatMap, который «распрямляет» один уровень вложенности.
Представим две операции: первая возвращает идентификатор, вторая по этому идентификатору загружает объект и тоже может завершиться ошибкой. Если применить map ко второй операции, тип станет вложенным: внешний Result будет содержать внутренний Result как успешное значение.
Такая структура усложняет обработку: придётся отдельно разбирать внешний и внутренний уровни. Кроме того, неверный выбор метода может привести к неудобной цепочке проверок и к ошибкам при выводе типов.
map имеет смысл использовать для функции вида «успех преобразуется в обычное значение». Например, Result<Int, E> после преобразования числа в строку станет Result<String, E>.
flatMap предназначен для функции вида «успех преобразуется в другой Result». Его логика такова:
failure, функция-преобразователь не вызывается, а ошибка передаётся дальше;success, его значение передаётся в функцию;Result становится итогом всей операции без дополнительного уровня вложенности.У flatMap обычно сохраняется один и тот же тип ошибки. Если последующая операция использует другой тип ошибки, сначала нужно привести ошибки к общему доменному типу, например через mapError. Это не делает flatMap универсальным средством объединения несовместимых типов ошибок.
В примере result имеет тип Result<String, LoadError>. Если использовать map { load(id: $0) }, типом стал бы Result<Result<String, LoadError>, LoadError>, потому что map воспринимает возвращённый внутренний Result как обычное успешное значение.
Компромисс заключается в том, что длинная цепочка flatMap может стать менее читаемой, особенно при сложной бизнес-логике. В таких случаях throws и последовательный do-catch часто лучше отражают императивный сценарий, тогда как Result удобнее для явной передачи состояния между слоями.
Сервис сначала разбирает внешний идентификатор, затем проверяет права и после этого загружает сущность. Каждая операция возвращает Result с доменной ошибкой.
Вариант с несколькими вызовами map создаёт вложенные Result и заставляет вызывающий код обрабатывать уровни по очереди. Вариант с ручным раскрытием каждого результата устраняет вложенность, но увеличивает шаблонный код и риск забыть передать ошибку.
flatMap выбран для последовательных операций, потому что он сохраняет плоский тип, останавливает цепочку на первой ошибке и передаёт успешное значение дальше. Для простого преобразования уже полученного значения используется map, а для преобразования типа ошибки — mapError. В результате слой сервиса получает один предсказуемый Result с единым типом ошибки.
Чем flatMap отличается от map по типам результата?
map принимает функцию, возвращающую обычное значение, и оборачивает его в Result. Если функция уже возвращает Result, появляется вложенность. flatMap принимает функцию, возвращающую Result, и возвращает этот внутренний результат напрямую, поэтому вложенность не возникает.
Будет ли вызвана функция flatMap, если исходный результат содержит ошибку?
Нет. При исходном failure преобразователь не вызывается, а ошибка передаётся в итоговый результат без изменений. Это обеспечивает короткое замыкание цепочки и предотвращает выполнение последующих операций, которым не хватает корректного успешного значения.
Что делать, если две последовательные операции используют разные типы ошибок?
Их нужно привести к единому типу до или во время композиции. Обычно применяют mapError, преобразуя технические ошибки отдельных операций в общий доменный тип. Простая замена map на flatMap не решает проблему несовместимых типов ошибок и не должна скрывать потерю важного контекста.