Обновить

Комментарии 9

Давайте упростим. А то тяжело читать, хоть я и математик по образованию.

Естественные преобразования - короткая версия

Зачем это нужно?л

В программировании мы работаем с разными "контейнерами" типов: ListOptionFuture и т.д. Часто нужно переходить от одного к другому, например 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]     // toObservable

3. Упрощение вложенных структур

Option[Option[A]] => Option[A]  // flatten

List[List[A]] => List[A]        // flatten

4. Интеграция библиотек

  • 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 лет, потому что потом становится слишком затратно/больно месить этот говнокод.

У нас есть выбор с каким кодом работать. Я - за качественный.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации