Комментарии 9
Давайте упростим. А то тяжело читать, хоть я и математик по образованию.
Естественные преобразования - короткая версия
Зачем это нужно?л
В программировании мы работаем с разными "контейнерами" типов: List, Option, Future и т.д. Часто нужно переходить от одного к другому, например List[A] => Option[A].
Проблема: Не все такие преобразования одинаково хороши. Некоторые ведут себя непредсказуемо при композиции с другими функциями.
Что такое естественное преобразование?
Это универсальная функция F[X] => G[X], работающая для любого типа X, которая коммутирует с функторами.
Условие естественности (простыми словами):
val f: A => B = ???val α: F ~> G = ??? // наше преобразование// ДВА ПУТИ должны давать ОДИНАКОВЫЙ результат:// Путь 1: сначала преобразовать F[A] => G[A], потом применить f// Путь 2: сначала применить f к F[A] => F[B], потом преобразовать F[B] => G[B]α[B](fa.map(f)) == α[A](fa).map(f) // должно быть TRUE всегда!Пример естественного преобразования:
val headOption: List ~> Option = [A] => (list: List[A]) => list.headOptionДве композиции
Вертикальная (обычная последовательность):
val α: F ~> Gval β: G ~> Hval result: F ~> H = β ⋅ α // сначала α, потом βГоризонтальная (когда функторы сами композируются):
val α: F1 ~> F2val β: G1 ~> G2val result: (G1 ∘ F1) ~> (G2 ∘ F2) = β ∘ αЕстественный изоморфизм
Два взаимообратных естественных преобразования:
val туда: F ~> Gval обратно: G ~> F// Должно выполняться:(обратно ⋅ туда)(fa) == fa(туда ⋅ обратно)(ga) == gaЭто означает, что F и G по сути "одинаковые", просто представлены по-разному.
Естественные преобразования - это "правильные" способы переходить между функторами, которые:
Работают предсказуемо
Композируются хорошо
Не зависят от порядка применения функций
Они нужны для построения монад, линз и других конструкций функционального программирования.
Практические применения естественных преобразований
1. Конвертация между контейнерами
List[A] => Option[A] // headOption
Option[A] => List[A] // toList
Try[A] => Either[E, A] // toEither
Future[A] => Task[A] // конвертация эффектов2. Работа с эффектами
// Запуск отложенных вычислений
IO[A] => Future[A] // unsafeToFuture
Task[A] => Observable[A] // toObservable3. Упрощение вложенных структур
Option[Option[A]] => Option[A] // flatten
List[List[A]] => List[A] // flatten4. Интеграция библиотек
Cats → ZIO
Akka Streams → FS2
ScalaZ → Cats
Java Optional → Scala Option
5. Тестирование
// Замена реальных эффектов на тестовые
Real[A] => Mock[A]
IO[A] => Id[A] // синхронное выполнение для тестов6. Оптимизация
Stream[A] => Vector[A] // материализация
LazyList[A] => List[A] // строгое вычисление7. Обработка ошибок
Either[E, A] => Validated[E, A]
Try[A] => IO[A]Зачем это программисту?
✅ Безопасность - гарантия, что преобразование не сломает логику
✅ Композируемость - можно свободно менять порядок операций
✅ Переиспользование - один раз написал, работает для всех типов
✅ Рефакторинг - можно менять контейнеры без изменения бизнес-логики
Композируемость - это скорее про саму возможность собирать из простого сложное, а не про изменение порядка операций.
А в целом всё верно. Краткий конспект на две минуты для десятиминутной статьи.)) Наверняка кому-то будет полезен, спасибо!
Супер
В таких же словах объясните монаду, пожалуйста. Тоже хтонь, но из программирования.
Ранее уже делился своими соображениями по поводу вопроса Программисту нужна математика?
А вот это всё — это, как говорится, wishful thinking
Уж не знаю, где так говорят... Но похоже, что мне не удалось донести очень важную идею. Перечисленные товарищем @Dhwtjпункты - это вовсе не попытки "выдать желаемое за действительное". Это именно мотивация для того, чтобы захотеть все эти абстракции, для которых математики уже просто придумали названия. Возможно, в это сложно поверить, но именно по этим причинам все эти абстракции появились в программировании, стали известны программистам.
Что, если сказать?..
Вот как раз программистам вовсе не нужно задавать такие вопросы. Наоборот, сначала (в том числе и в программировании!) возникает необходимость, для которой формулируются задачи, решениями которых становятся все эти абстракции. То, что они были известны математикам раньше, не имеет большого значения - математики и так знают много того, что программистам особо не нужно.
Дело не в том, что программистам "нужны все эти морфизмы". Решение задач программирования требует инструментов, для которых должны выполняться определённые законы. Как раз законы этих инструментов и определяют абстракции, для которых у математиков уже есть свои названия. Именно это я пытаюсь раскрыть в своём обзоре.
Если докопаться до основ ФП, то там стройная но сложная математика.
Если докопаться до основ ООП, то там треш, угар и содомия. Но на поверхности всё хорошо и удобно: кошечки, собачки. А при параллельных запросах треш. Композиция треш. Доказательства корректности треш.
Alan Kay - message passing, изоляция. Слишком медленно! shared mutable state + блокировки
То, что они были известны математикам раньше, не имеет большого значения
Это форсайт, предвидение
Нет ожиданий — нет разочарований.
Я понимаю такую позицию, но не разделяю её. Страх перед разочарованиями отнимает мечты и стремление к лучшему.
Кстати, самое смешное, что упомянутая выше
lstToOptionне является естественным преобразованием.
Вполне себе является. Естественные преобразования определяются только в контексте чистых функций, а f ≔ x ↦ 1 / x не является таковой на значениях целочисленного типа.
А они стали известны программистам?
Безусловно. Я же говорил не обо всех программистах, а о тех, кто с ними работает, например, в стеке CE или ZIO в Scala. Сам я познакомился с ними ещё будучи C#-разработчиком - уже тогда искал и находил более выразительные ФП-шные способы решения задач. И уж тем более эти абстракции известны разработчикам языков, которые лучше других понимают, какие инструменты будут полезны программистам.
Сколько людей пишет обычный продуктовый код...
...нет ничего зазорного в том, чтобы зарабатывать на хлеб унылым с теоретической точки зрения программированием...
Как же незнакомые с этими абстракциями люди решают задачи?
Да хреново они решают!)) Это вовсе не попыктка кого-то обидеть, это очевидный факт. Я на собственном опыте оценил качественный скачёк при переходе на ФП-шное мышление. Типичная кодовая база на популярных ЯП (в которых обычно никто не ожидает понимания ФП с его математическим базисом) - это тонны слаботипизированной копипасты, которую крайне тяжело читать и рефакторить. Тестирование просто не способно закрыть все дыры и явные ошибки, которыми полнится такой код. И это притом, что даже в популярных ЯП давно уже завезли кучу всякой функциональщины. С тех же плюсов я слез уже много лет назад, но помниться и тогда много вкусностей было во всяких stl/boost. Но очень много программистов просто не знакомо с этим - у них нет какой-либо математической базы чтобы даже захотеть поискать лучшие решения! В основной массе все говнокодят постаринке, и да, им платят за это деньги. Но такие проекты живут 3-5 лет, потому что потом становится слишком затратно/больно месить этот говнокод.
У нас есть выбор с каким кодом работать. Я - за качественный.

Категории типов. Часть 3. Естественные преобразования