Представьте, что вы сидите за рабочим столом, допиваете кофе и готовитесь к написанию кода на Scala. Функциональное программирование оказалось не так страшно, как его малюют, жизнь прекрасна, вы усаживаетесь поудобнее, сосредотачиваетесь и начинаете писать новый функционал, который нужно сдать на этой неделе. Всё как обычно: несколько лаконичных однострочных выражений (да, детка, это Scala!), несколько странных ошибок компилятора (о, нет, Scala, нет!), лёгкое сожаление о том, что вы написали такой запутанный код… И вдруг вы сталкиваетесь со странной проблемой: выражение for не компилируется. «Ничего страшного», — думаете вы: «сейчас гляну на StackOverflow», как вы это делаете ежедневно. Как все мы это делаем ежедневно. Но сегодня, похоже, неудачный день. Сначала вам кажется, что лучший ответ слишком заумный. Обычно достаточно прокрутить вниз, найти более простое решение и забыть про объяснение с привлечением теории категорий, монад, и всего такого без особых угрызений совести. Однако на этот раз второй ответ похож на первый, и третий, и четвертый. Что происходит? Монады. Трансформеры. Даже названия звучат пугающе. Давайте посмотрим внимательнее, в чём ваша проблема? Сначала вы написали функции: Это выглядело элегантно: класс Future представляет асинхронное вычисление, и у него есть метод flatMap, что означает, его можно поместить в выражение for. Супер! Затем вы внезапно поняли, что не для каждого идентификатора существует пользователь. Что делать, если пользователь не найден? Ну хорошо, класс Option служит как раз для этой цели: И, если на то пошло, некоторые пользователи могут не иметь адреса: Но когда вы вернулись к этому коду, появилась ошибка компиляции: Да, всё верно, ведь тип возвращаемого значения теперь Future[Option[Address]]: Компилятор должен быть доволен. Но что он пишет? Нехорошо. Подумав немного, вы вспомнили, что <- — это просто удобный способ вызвать метод flatMap, и если вы его вызвали на объекте типа Future[Option[User]], то получили Option[User], хотя вам нужен объект User... Вы попробовали так и эдак, но всё не то. Лучшее, что вы смогли придумать, выглядело следующим образом: Некрасиво или, по крайней мере, не так красиво, как было прежде. В идеале вам хотелось что-то вроде: Такой себе метод flatMap, который сразу извлекает значение и из Option, и из Future. Но на StackOverflow никто про него не упоминал... Всё-таки, в чём загвоздка? Почему нет суперметода flatMap который работает с объектами типа Future[Option[X]]? Эти базовые знания из теории категорий помогают разгадать загадку. Если у вас есть два функтора A и B (то есть вы можете вызвать метод map на объектах класса A[X] и на объектах класса B[X]), вы можете их скомпоновать, не зная больше об этих классах ничего. Вы можете взять класс A[B[X]] и получить Functor[A[B[X]], скомпоновав Functor[B[X]] и Functor[A[X]]. Другими словами, если вы знаете, как отображать внутри A[X] и внутри B[X], вы также умеете отображать внутри A[B[X]]. Автоматически. Без усилий. Для монад это неверно: умение выполнять flatMap над A[X] и B[X] не дает вам автоматической возможности выполнять flatMap над A[B[X]]. Оказывается, это хорошо известный факт: монады не компонуются, по крайней мере, в общем случае. Ну ладно, монады не компонуются в общем случае, но вам нужны методы flatMap и map, которые работает на объектах класса Future[Option[A]]. Мы это точно сможем. Давайте напишем обёртку для Future[Option[A]] с методами map и flatMap: Неплохо! Давайте её используем! Работает!  Хорошо, это если у вас объект типа Future[Option[A]]. Но что, если у вас есть, скажем, List[Option[A]]? Может быть, поможет другая обёртка? Давай попробуем: Ага, она похожа на FutOpt, да ведь? Если присмотреться, понятно, что нам не нужно ничего знать о «внешней» монаде (Future или List из предыдущих примеров). Пока мы умеем выполнять map и flatMap, всё отлично. С другой стороны, помните, как мы анализировали объект Option? Нужно знать специфику «внутренней» монады (в данном случае это Option), которая у нас имеется. Получается, мы можем написать общую структуру данных, которая «обертывает» любую монаду M вокруг класса Option. Потрясающее известие: мы случайно придумали монадный трансформер, который обычно называют OptionT! OptionT имеет два параметра F и A, где F — обертывающая монада, а A — тип внутри Option. Другими словами, OptionT[F, A] является плоской версией F[Option[A]] и имеет методы map и flatMap. Обратите внимание, что класс OptionT также является монадой, поэтому мы можем использовать его в выражении for (в конце концов, мы для этого и городили огород). Если вы используете библиотеки наподобие cats, многие монадные трансформеры (OptionT, EitherT, ...) в них уже есть. Вернемся к нашему изначальному примеру: Работает! Можем ли мы ещё что-то улучшить? Возможно, если мы всё время используем обёртки, стоит возвращать OptionT[F, A] из этих методов: И это очень похоже на наш первоначальный код. А когда нам нужно фактическое значение типа Future[Option[Address]], мы можем просто вызвать value. Прежде чем завершить статью, небольшое предостережение: Добавлю, что монадные трансформеры — это лишь один из способов разделаться со вложенными монадами. Они подходят, если у вас простая проблема и вы не хотите сильно менять код, но если вы готовы к бóльшему, обратите внимание на библиотеку Eff. Итак, повторюсь, монадные трансформеры помогают нам в работе с вложенными монадами, предоставляя плоское представление двух вложенных монад, и сами являются монадами. Надеюсь, я доказал, что они не так страшны, как называются, и что вы могли бы придумать их сами (а может, уже и придумали в той или иной мере). Для них есть стандартные прикладные случаи применения, но не злоупотребляйте ими. Если вы хотите узнать больше по этой теме, я рассказывал о монадных трансформерах на конференции Scala Italy в прошлом году: https://vimeo.com/170461662 Счастливого (функционального) программирования! Разработчик Scala