Comments 11
Вот сколько я уже пытался понять, что такое "функциональное программирование", в том числе путём чтения статей на Хабре - безуспешно... Я понимаю, конечно, что, возможно, мне не хватает образования и опыта, но вот какая штука - чтобы понять, что такое ООП, хотя бы на практическом уровне, мне было достаточно весьма незначительного опыта, буквально пары книг и некоторого практического опыта использования С++. Я не долбил паттерны, SOLID и прочее - поскольку это всё в ООП вторичные вещи, проистекающие из самой парадигмы... Когда же я сталкиваюсь с попытками объяснения, что такое ФП, и на кой оно, собственно, нужно при наличии ООП, - хотя бы на базовом уровне - начинаются какие-то огромные статьи, со схемами и стрелками, совершенно зубодробительными примерами кода, отсылками в весьма тяжёлые для несильно посвященного (ну, не всем же фанатеть от теории категорий или каких-нибудь канторовых пространств) области математики. Создаётся ощущение, что без этого понять что-либо в ФП совершенно невозможно. Тем не менее ФП весьма активно проталкивается во вполне себе ОО ЯП (типа того же C#), превращая их в какую-то смесь бульдога с носорогом, подобно похожей истории, когда во вполне себе языки со строгой типизацией начали впихивать скриптовые Var'ы.
Я далек от критики - чтобы критиковать, надо понимать предмет, а вот с пониманием, как раз, проблемы - и внятного ответа в довольно-таки большом количестве текстов пока нет, ИМХО. А статья, наверное, прекрасная...
Мне тоже поначалу казалось, что я понимаю ООП. Но знакомство с ФП и сравнение их друг с другом внезапно дало больше понимания парадигмы ООП. "Полиморфизм" там понимается крайне ущербно и вообще извращённо, инкапсуляция полезна лишь для весьма узкого класса задач (а вовсе не повсеместно!), параноидальное "сокрытие" призвано покрывать архитектурные косяки и привычные мутабельные переменные, а "наследование" - вообще мертворождённая идея, привносящая огромное количество проблем, но с которыми все уже так привыкли бороться... И поверх всего это понапридумывали костыльные "паттерны"... ООП оказалось реально переусложнённым по сравнению в ФП, как синтаксически, так и семантически. Со всеми вытекающими (привычными для всех!) проблемами разработки.
А вот ФП концептуально гораздо проще. Мы можем определять функции и вызывать их. И на базовом уровне - это всё! Пожалуй, самой характерной особенностью является то, что функции можно комбинировать друг с другом не усложняя код упоминанием промежуточных значений (`h = f andThen g`).
Для лучшего обеспечения качества программ ещё на этапе компиляции нам требуются статическая типизация, иммутабельность переменных (в ФП - по умолчанию) и "чистые" реализации функций.
Значительное усложнение происходит лишь когда дело доходит до обобщённых типов. А они реально полезны - позволяют значительно упросить код - не зря их добавляют во все популярные (оопшные!) языки. Но только делают это в крайне урезанном виде упуская большое количество возможностей, которые есть в том же Scala.
Именно обобщённым типам посвящен мой цикл обзоров. Их иногда обзывают и "функторами" и "монадами" - эти загадочные слова отпугивают многих программистов. В данном обзоре я хочу разобраться во всех этих деталях.
ООП и функциональное программирование это два ортогональных способа защитить состояние от несанкционированного изменения. В ООП состояние спрятали в приватных полях. А в функциональном программировании вообще от состояния отказались.
Защита состояния от несанкционированного изменения - это лишь одна небольшая задача, которую нужно решать программистам. ОПП и ФП гораздо больше, чем просто разные способы решения этой задачи.
В ФП не "отказывались" от состояний. Там их нет изначально. А задача "хранения состояния" - это лишь один из редковостребованных побочных эффектов, с каждым из которых приходится работать с особой осторожностью.
По сути, ООП это надстройка из граблей, появившаяся из-за необходимости, о которой уже мало кто помнит, и распространившаяся из-за колоссальных финансовых вливаний одной корпорации в её популяризацию. Просто у большинства программистов уже выработались крепкие мозоли от этих граблей - не так то просто от них отказаться))
Монады не композируются, но есть интересное направление с алгебраическими эффектами, для которых такого ограничения вроде бы нет: https://en.m.wikipedia.org/wiki/Effect_system
Причём если посмотреть на язык Koka, то там эффектами выражаются штуки типа чистоты функций, возможности расходимости (незавершения), бросание исключений и т.п.: https://koka-lang.github.io/koka/doc/book.html#sec-effect-types
И эффекты позволяют выразить то же самое, что делают монады типа Try или Option.
Так вот, можно ли смотреть на алгебраические эффекты как на альтернативу монадам в общем случае?
Альтернативы собирался рассмотреть в самой последней части обзора (ориентировочно в седьмой))). И конечно же, собираюсь рассказать там про алгебраические эффекты. Но маленький спойлер - радикального устранения всех ограничений нет нигде. Инструментарий монад достаточно фундаментален, и любые другие системы будут либо хуже, либо изоморфны монадам. Разве что возможно какими-то выкрутасами получится уменьшить боли. Посмотрим))
А про язык Koka не слышал. Навскидку выглядит так, что это какое-то усложнение - полиморфные типы отдельны, а контейнеры эффектов отдельно... Обязательно посмотрю получше. Спасибо!
Вроде бы всё понятно... и дальше понятно...
потом появилась строчка
val morphism3: Hom[Double, Int ] = morphism1 andThen identity[String] andThen morphism2
и стало непонятно. Ничего не понятно. Зачем тут identity?
Ну а дальше вообще. Более чем непонятно.
andThen (также как и compose) - это встроенный в Scala комбинатор функций. Он из двух функций делает одну, передающую выход первой функции на вход второй. Встроенную функцию identity я упомянул только для демонстрации того, как функция композиции (andThen) работает с тривиальным морфизмом. identity просто пробрасывает на выход то, что прилетело на вход. Композиция с тривиальным морфизмом просто сохраняет другой морфизм без изменений.
Все эти диаграммы типов и функций прекрасным образом укладываются в систему
— просто типизированного лямбда-исчисления. Эта система полная, она позволяет успешно решать все задачи программирования.
Ну ведь нет, в просто типизированном лямбда исчислении нельзя формализовать понятие бесконечного цикла т.к. просто нет типа соответствующего такого рода функции, что делает систему не полной по Тьюрингу(если это имелось ввиду).
Категории типов. Часть 1. Hom-типы