Начинаю тему и приглашаю в нее тех, кто лучше меня в этом разбирается
Для каких именно практических нужд пригодны монады? и что называется монадой в языках, отличных от Хаскеля?
Как-то более или менее понятно, что конкретно в lazy eval Хаскеле нет возможности гарантировать порядок исполнения, иначе чем через f(g(x))
И как-то более или менее понятно, что конкретно монада IO и сделана для того, чтобы после разворачивания bindов получить именно f(g(x)) с гарантией порядка, что очевидно нужна вводу-выводу.
Более или менее понятно, что такое Maybe и зачем оно.
Более или менее понятно, что на монадах можно сделать промис из JS, для этого bind в некоторых случаях должен не звать свою лямбду, а запихивать ее в новый объект, откуда она позовется когда-то потом. Как-то так.
Не очень понятно, для чего еще годится этот механизм.
Попробую выразить свое понимание, что такое монада в Хаскеле вообще:
— Monad T есть обертка вокруг значения T
— при этом обертка динамическая, т.е. "внутренность" может и не лежать всегда в памяти, а может строиться при обращении
— >>=, он же bind, извлекает из монады внутренность и отдает ее туда (в ту лямбду), куда просили. Т.е. "разворачивает".
— при этом монадная лямбда обязана вернуть "завернутую" монаду. Для этого есть return, который по внутренности строит монаду (в простейшем случае кладет ее внутрь новой монады). Т.е. "оборачивает".
— при написании байндов и возвратов для своих монад нужно соблюдать тождества вида "Завернуть(Развернуть(M)) = M".
Получается старый добрый декоратор который можно реализовать в обычных ОО языках наследованием с перекрытием пабликов. Единственное: при этом объекты будут "по ссылке", а монада позволяет "по значению".
Я прав? а в других языках как?
При этом есть еще такой хитрый трюк: список есть монада, и bind у него — итерация по списку. Это как-то выглядит немного ни к селу, ни к городу.
MSS>Не очень понятно, для чего еще годится этот механизм.
Жизнь (и сильная команда) заставили писать в суровом монадном стиле на scala. Месяц пишу, два пишу, уже даже привык. Но каждый день хочется орать bullshit, потому что монад дохера, а щастя мало
В общем самая соль монад — что ты работаешь с данными известного (или неизвестного) типа, не зная того в какой они конкретно обёртке. Например монада Id (в scala, не знаю как у других называется) — это просто сами данные, обёртка без обёртки и по идее должна быть выкинута полностью на этапе компиляции. Монада Option — о том что данные или есть или их нет. А монада Task — позволяет один и тот же алгоритм применить к асинхронно доставаемым данным. Сам же алгоритм, если он реально под монады сделан, вообще не должен ничего предполагать о том в какой обёртке данные придут. Он работает непосредственно с данными и просто знает что они будут в какой-то обёртке.
Самый простой пример — у тебя есть функция сложения Int-ов завёрнутых в какие-то монады. Если эту операцию применить к монаде Id — получишь сложение значений. Если к List то сложение списков. Если к Task, то данные для сложения будут браться откуда-то асинхронно, и т.п.
В идеале этот механизм позволяет делать ну очень обобщённые алгоритмы. Например — ты сделал сложный механизм обработки каких-то больших данных. Если ты его реализуешь допустим в C# — ты можешь выбрать одно из двух — или сделать API асинхронным для поддержки асинхронных вызовов. Или синхронным. (Вариант когда синхронное API просто обёртка над асинхронными вызовами пока выкидываем).
На монадах — в зависимости от типа монады у пользователя твоей либы после компиляции получится либо чисто асинхронный код (если он применил Task-и), либо чисто синхронный (в идеале без всякого оверхеда на обёртки, если он взял данные из массива и кормит их тебе через Id-монаду). Либо пользователь сможет не только получить обработку данных, но ещё и сохранить какую-то свою информацию незаметно для обрабатывающего (монада State). В реальности же (ну по меньшей мере в scala, не знаю как в haskell) — оверхед на эти обёртки местами адовый — из-за того что много лямбд, много объектов создаётся и удаляется на каждую операцию, и т.д. Конечно тут любой воскликнет "да я на ООП тоже самое смогу" (я кричал), но нет. Получается не совсем тоже самое, с монадами возможностей немного больше. Но пока цена за эти возможности (ИМХО) перевешивает и сильно, профит от их использования. Может конечно через год ежедневного использования монад я буду думать иначе.
Что мне нравится в этих монадах, так это то что очень красиво решается задача обработки данных с сохранением контекста того откуда данные пришли, причём сам контекст и его существование невидимы для обрабатывающих. И вообще сохранение (и скрытие) любых контекстов. Вот у тебя есть например алгоритм добавления денег на счёт пользователя по какому-то условию. И если всё грамотно запихать в монады, то сам алгоритм не будет понятия иметь ни о пользователе (т.е. буквально, на вход к нему никакого там экземпляра User не будет приходить), ни о том откуда это пользователь взялся, ничего лишнего. И если ты решишь добавить пользователю мультивалютные кошельки — у тебя есть вариант заиспользовать существующий код не меняя его вовсе, просто данные будут приходить обёрнутые в монаду которая знает о конкретном кошельке. А код который зачисляет — останется тем же.
Есть мечта попытаться выразить эту монадную магию как-нибудь синтаксически (скорее всего в Nemerle/Nitra где ж ещё). Потому что тот ад типизации вокруг монад, который я везде видел, взлетает только у самых упоротых гиков.
Здравствуйте, hi_octane, Вы писали:
_>В общем самая соль монад — что ты работаешь с данными известного (или неизвестного) типа, не зная того в какой они конкретно обёртке.
Никогда эту тему не изучал, но всегда казалось, что монады нужны для IO или взаимодействися с внешним миром, т.е. там где необходимо состояние (работа с). Как известно в фп состояния нет, ибо зло, но как-то взаимодействовать с
внешним миром надо, вот и придумали какой-то механизм.
Или монады нечто большее или совсем другое?
S>Или монады нечто большее или совсем другое?
IO вылазит на поверхность чаще всего остального по-моему в основном потому что пуристы чистой функциональщины очень рады её существованию и от радости упоминают чаще других — без IO мир неизменяемых чистых функций имел бы одну большую дырищу в месте соприкосновения с реальностью, в которой чистые функции это весело, но работа нужна только если производит какие-то изменения в мире.
Сами монады это всё-таки большее. Причём мне кажется что в книжках и статьях их объясняют очень неправильно. Прежде всего — если будешь читать, игнорируй все аналогии с "значение в контейнере". Контейнер с переменной это частный случай, который только запутывает (меня точно запутали). Правильное понимание будет если под контейнером всегда, даже если используется самая простая обёртка типа Option, понимать что там лямбда (замыкание). И эта лямбда никогда никому не вернёт то значение которое у неё внутри. Она лишь даёт интерфейс (тот самый bind/map) который позволит тебе передать в лямбду функцию, которая когда-то (а может никогда) таки получит на вход то самое значение которое этот контейнер как будто хранит. Да в случае самых простых обёрток подразумевается что значение в контейнере есть и всегда доступно (Option/List/Id). Но сама идея монад в том что они дают единообразный интерфейс для общего случая, когда значение в контейнере может есть, а может нет, а может есть но не значение а исключение, а может вообще их там не одно а целый список. А может там вообще бесконечный итератор на yield return. Например выделенный в C# LinqRx и Observavle<Int> для языков с монадами — ничуть не отличается от списка например. Нет какого-то отдельного синтаксиса, или там выделенных типов. И это прикольно.
С точки зрения математики да, всё круто и унифицировано. И итерация по элементам для list совершенно естественна. Более того если бы кто-то не поленился и развил эту идею мы бы наверное имели сейчас какой-то реально иной язык программирования — ведь что такое в C# "int?" — это список из 0 или одноой значения (монада option). Что такое "int" — это список с ограничением на ровно одно значение. List<int> — это список с нулём или более значений, а void (unit) — это список с ровно 0 элементов. А Observable<Int> — для языков с монадами это тоже Monad<Int>, просто в ней от 0 до бесконечности значений.
Если бы это всё были монады а не разные захардкоженные сущности, то с точки зрения монад это всё абсолютно одно и тоже, с единым способом работы со всеми этими штуками. Нет никаких причин (и даже способов) различать во что обёрнуты значения, главное что это Monad<Int>. И если бы это было и для языка одним и тем же за счёт годного синтаксического сахара, т.е. если бы все переменные были монадами, то Dictionary<void, int> — это естественным образом получался бы List. А Dictionary<int, void> — это Set. И написав один раз код на таком языке для обработки одного значения, этот же самый код, без всяких изменений, мог бы стать параллельным — просто входной параметр типа Monad<Int> одинаково принимает и просто Int, и IList<Int>, и Observable<Int>. И в случае списка ты автоматом получишь последовательное или параллельное (в зависимости от деталей реализации обёртки) выполнение, а может даже распределённое или на GPU, а не как сейчас — ошибку типизации.
А ещё, из-за того что в наших языках программирования все переменные так или иначе внутри множества — можно работу с отдельными элементами внутри монад описать более обобщённо (в терминах колец, групп и т.д.) но я там теряюсь и с людьми которые реально в этом шарят пока беседу поддержать не могу Там всё очень алгебраично (для тех кто этот мем пропустил см. под катом)
Здравствуйте, Sharov, Вы писали:
S>Или монады нечто большее или совсем другое?
Гораздо большее. Это общее свойство многих вычислений. Где детали не учитываются, а берется самое главное: (1) способность создавать новое вычисление по примитиву (a.k.a "чистому" значению) и (2) возможность продолжить старое вычисление с помощью нового, которому передается как бы "результат", полученный в ходе первого вычисления, и тогда получается комбинированное вычисление. Это все делает такие вычисления композиционными. Они становятся строительными блоками, из которых можно создавать новые вычисления.
Например, монадой можно описать активность дискретного процесса в дискретно-событийном моделировании, а "стрелкой" тогда (связанное понятие) — некоторый вариант блоков (карт) из языка моделирования GPSS.
Применительно к программированию, одно из удивительных применений монад — это возможность изящно записывать асинхронные вычисления и также изящно работать с так называемой "транзакционной памятью" (Software Transactional Memory) для укрощения многопоточности, где вот как раз сложные вещи создаются, комбинируя между собой простые. Поэтому собственно очень редко, когда хаскеллиста или скалиста можно заставить писать на каком-то другом языке программирования
Но я понимаю, что без практики это все сложно понять, да и невозможно. Я сам когда столкнулся с этим, то по первой ничего не понял из тех описаний общими словами, что видел.
Сразу замечу, что компилятор хаскеля транслирует монады очень эффективно, насколько возможно, часто убирая почти весь дополнительный слой абстракций, который здесь возникает. Собственно, поэтому по вычислению в монаде IO порождается практически такой же код, как и обычный код там на С или C++.
Здравствуйте, Maxim S. Shatskih, Вы писали:
MSS>Для каких именно практических нужд пригодны монады?
Мой любимый пример — парсер-комбинаторы.
А так, хорошее нетривиальное применение монад сложно встретить. В Хаскеле на них выражают эффекты — исключения, недетерминизм, ввод-вывод, мутабельное состояние и всевозможные их комбинации. Т.е. то, что в других языках уже и так есть обычно.
MSS>и что называется монадой в языках, отличных от Хаскеля?
Есть похожие на Хаскель языки, вроде Идриса, там монады такие же. А в более других языках монадами обычно или вообще ничего не называют (да и выразить монаду явно в большинстве языков нельзя), или условно обзывают монадами те же промисы и футуры, причем не всегда корректно.
Лет 8-10 назад на монады был большой хайп, каждый адепт ФП должен был написать свое объяснение монад и указать на то, что неявно монады в других языках есть (но мужики не знали), а явно хрен выразишь по-человечески. Только на Хаскеле да на Скале. А сейчас, вроде, хайп приутих. Кто пишет на хаскеле, автоматически их использует, они там везде в стандартной библиотеке и без них там сложно. Кто на Скале и вокруг заметили, что во многих случаях достаточно аппликативных функторов, а не монад.
Чтобы писать красивый монадный код, который бы работал в разных априори неизвестных монадах, надо, во-первых, чтобы система типов позволяла абстрагироваться не только от типа Т, но и от контейнера (функтора) F<T>, т.е. нужны higher-kinded types. А их кроме хаскеля со скалой нигде нет практически, даже в расте нет. А во-вторых нужна поддержка синтаксиса, та же do-нотация, этого тоже нигде особо нет. Так что монады как-то в другие языки не пошли.
D>Применительно к программированию, одно из удивительных применений монад — это возможность изящно записывать асинхронные вычисления и также изящно работать с так называемой "транзакционной памятью" (Software Transactional Memory) для укрощения многопоточности, где вот как раз сложные вещи создаются, комбинируя между собой простые. Поэтому собственно очень редко, когда хаскеллиста или скалиста можно заставить писать на каком-то другом языке программирования
DM>Чтобы писать красивый монадный код, который бы работал в разных априори неизвестных монадах, надо, во-первых, чтобы система типов позволяла абстрагироваться не только от типа Т, но и от контейнера (функтора) F<T>, т.е. нужны higher-kinded types. А их кроме хаскеля со скалой нигде нет практически, даже в расте нет. А во-вторых нужна поддержка синтаксиса, та же do-нотация, этого тоже нигде особо нет. Так что монады как-то в другие языки не пошли.
Вот что в скале бесит (и наверное в хакселе, хоть я на нём и не пишу, но сорцы иногда читаю) — так это то что эта самая do-нотация нужна в явном виде. А в скале — так это запихнуто в for-comprehension, который спотыкается на простейших вещах (типа нормально иф в этот фор всунуть). Неужели нельзя добавить сахара в компилятор чтобы он распознавал код на монадах, и правильно его рассахаривал без do-нотации? Вроде задаче не сложнее чем та которую решает компилятор C# преобразуя функцию с async/await.
Здравствуйте, hi_octane, Вы писали:
MSS>>Не очень понятно, для чего еще годится этот механизм. _>Жизнь (и сильная команда) заставили писать в суровом монадном стиле на scala. Месяц пишу, два пишу, уже даже привык. Но каждый день хочется орать bullshit, потому что монад дохера, а щастя мало
Какую IDE используете? Мне очень мешает IDEA, т.к. она часто плохо распознает монадный код, подсвечивая красным корректные выражения и не подсвечивая некорректные.
cats или scalaz? Знаете какой-нибудь сурово-монадный опнсорс проект на скале?
Мне глубоко интересна эта тема, но в ней окопалось много теоретиков, предлагающих нежизнеспособные решения. И никто из них не может показать код.
scf>Какую IDE используете? Мне очень мешает IDEA, т.к. она часто плохо распознает монадный код, подсвечивая красным корректные выражения и не подсвечивая некорректные.
IDEA вроде не отвечает за подсветку scala. Отвечает scala plugin, который по ощущениям в основе сам компилятор скалы. Соответственно если код настолько суров, то плохая раскраска будет везде.
Стандарт только на исходники, на местах используют кто что хочет — тут есть и маководы с IDEA, и принципиальные линуксоиды с консолью (наверное vim, но какой-то шибко цветной для vim'a), и кто-то с чем-то похожим на Far.
scf>cats или scalaz? Знаете какой-нибудь сурово-монадный опнсорс проект на скале?
Увы, в скала-проектах пока страшный ламер, так что советовать мне ещё очень рано. Но всё что контора куда я попал делает, она делает в полнейшем опен-сорс, и хочется верить что там годный код Тот же cats используется постоянно. https://github.com/rchain/rchain/
Особенно мощно задвигают ребята в под-проекте rholang, ну и mtl / tagless-final в node и communication. Там прям эталонный пример как используются "эффекты" для декомпозиции сетевого кода до состояния, когда в тестах сетевой код тестируется, а при этом никаких соединений с сетью нет и в помине.
scf>Мне глубоко интересна эта тема, но в ней окопалось много теоретиков, предлагающих нежизнеспособные решения. И никто из них не может показать код.
Если прям интерес-интерес есть — могу контакт для связи подогнать, прособеседуешься и начнёшь сам хреначить. За следующий месяц человек 5 добрать хотят. Чистая удалёнка, не гербалайф.
Здравствуйте, Sharov, Вы писали:
S>Здравствуйте, dsorokin, Вы писали:
D>>Применительно к программированию, одно из удивительных применений монад — это возможность изящно записывать асинхронные вычисления и также изящно работать с так называемой "транзакционной памятью" (Software Transactional Memory) для укрощения многопоточности, где вот как раз сложные вещи создаются, комбинируя между собой простые. Поэтому собственно очень редко, когда хаскеллиста или скалиста можно заставить писать на каком-то другом языке программирования
S>А можно подробнее?
Здравствуйте, D. Mon, Вы писали:
DM>Чтобы писать красивый монадный код, который бы работал в разных априори неизвестных монадах, надо, во-первых, чтобы система типов позволяла абстрагироваться не только от типа Т, но и от контейнера (функтора) F<T>, т.е. нужны higher-kinded types. А их кроме хаскеля со скалой нигде нет практически, даже в расте нет. А во-вторых нужна поддержка синтаксиса, та же do-нотация, этого тоже нигде особо нет. Так что монады как-то в другие языки не пошли.
В Rust приемы есть разные. Для монад удобно определить не F<T> а trait F { type T; }. И если использовать FnOnce, то тогда все заведется. Даже монаду Cont можно изобразить, о чем было невозможно даже мечтать на C++.
Здравствуйте, hi_octane, Вы писали:
scf>>Мне глубоко интересна эта тема, но в ней окопалось много теоретиков, предлагающих нежизнеспособные решения. И никто из них не может показать код. _>Если прям интерес-интерес есть — могу контакт для связи подогнать, прособеседуешься и начнёшь сам хреначить. За следующий месяц человек 5 добрать хотят. Чистая удалёнка, не гербалайф.
Здравствуйте, hi_octane, Вы писали:
_>Вот что в скале бесит (и наверное в хакселе, хоть я на нём и не пишу, но сорцы иногда читаю) — так это то что эта самая do-нотация нужна в явном виде. А в скале — так это запихнуто в for-comprehension, который спотыкается на простейших вещах (типа нормально иф в этот фор всунуть).
А что не так в for-comprehension? Вроде все как в хаскеле там с if-ом:
import cats.effect.IO
val printJohn = IO { println("John!") }
val printSusan = IO { println("Susan!") }
val getName = IO { "Susan" }
val program: IO[Unit] =
for {
name <- getName
_ <- if(name == "John") printJohn else printSusan
} yield {}
N>А что не так в for-comprehension? Вроде все как в хаскеле там с if-ом.
Ну с моей колокольни это искусственная конструкция которая выглядит как костыль, ходит как костыль и крякает как костыль.
N>
А этот же вариант, но вместо printJohn нужно целую пачку действий, а вместо printSusan другую? Я сколько советов не спрашивал — гуру говорят выноси в локальную функцию а в ней свой for. И это костыль, потому что не должен язык навязывать композицию кода, да ещё такую. В C# эту проблему решили чуть более естественно в async/await, но увы в CLR нет higher-order types, так что нет и монад.
Здравствуйте, hi_octane, Вы писали:
_>И это костыль, потому что не должен язык навязывать композицию кода, да ещё такую. В C# эту проблему решили чуть более естественно в async/await, но увы в CLR нет higher-order types, так что нет и монад.
N>http://monadless.io/?
Спасибо, не знал про такую. Попробую (пусть даже в проект не дадут втащить, так хоть для себя), по итогу отпишу. Будет здорово если потянет всё что я постоянно использую.
Здравствуйте, hi_octane, Вы писали: _>Если бы это всё были монады а не разные захардкоженные сущности, то с точки зрения монад это всё абсолютно одно и тоже, с единым способом работы со всеми этими штуками. Нет никаких причин (и даже способов) различать во что обёрнуты значения, главное что это Monad<Int>. И если бы это было и для языка одним и тем же за счёт годного синтаксического сахара, т.е. если бы все переменные были монадами, то Dictionary<void, int> — это естественным образом получался бы List.
Не получится. У словаря нет порядка. У списка есть.
Далее есть множество и мультимножество. Они оба нужны. Но вот такой вот параметризацией из словаря их не получишь.
Если уж говорить об обобщении коллекций нужно начинать с чего-то типа этого https://www.boost.org/doc/libs/1_67_0/libs/multi_index/doc/index.html _>И в случае списка ты автоматом получишь последовательное или параллельное (в зависимости от деталей реализации обёртки) выполнение, а может даже распределённое или на GPU, а не как сейчас — ошибку типизации.
Баловство это. Если нужно что-то распараллелить, то нужно брать вот эту штуку или что-то похожее. http://halide-lang.org/ _>Там всё очень алгебраично (для тех кто этот мем пропустил см. под катом) _>
Здравствуйте, D. Mon, Вы писали:
DM>Чтобы писать красивый монадный код, который бы работал в разных априори неизвестных монадах, надо, во-первых, чтобы система типов позволяла абстрагироваться не только от типа Т, но и от контейнера (функтора) F<T>, т.е. нужны higher-kinded types. А их кроме хаскеля со скалой нигде нет практически, даже в расте нет.
Есть в C++, сам же знаешь
DM>А во-вторых нужна поддержка синтаксиса, та же do-нотация, этого тоже нигде особо нет. Так что монады как-то в другие языки не пошли.
Здравствуйте, hi_octane, Вы писали:
_>Вот что в скале бесит (и наверное в хакселе, хоть я на нём и не пишу, но сорцы иногда читаю) — так это то что эта самая do-нотация нужна в явном виде. А в скале — так это запихнуто в for-comprehension, который спотыкается на простейших вещах (типа нормально иф в этот фор всунуть). Неужели нельзя добавить сахара в компилятор чтобы он распознавал код на монадах, и правильно его рассахаривал без do-нотации? Вроде задаче не сложнее чем та которую решает компилятор C# преобразуя функцию с async/await.
Альтернативой DO-нотации (то есть альтернативой нарезки кода на замыкания, пусть и автоматической) является call-with-current-continuation — среда выполнения в явном виде отдаёт нам всё необходимое для многократного продолжения с текущего места.
Это например позволяет оманадить код не меняя привычного синтаксиса. Вот конкретный пример
Здравствуйте, hi_octane, Вы писали:
_>В C# эту проблему решили чуть более естественно в async/await, но увы в CLR нет higher-order types, так что нет и монад.
Даже если были бы — await это всё равно не то, например монаду List на нём не выразить, так как нужна поддержка многократного запуска продолжения с одного места.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
DM>>Чтобы писать красивый монадный код, который бы работал в разных априори неизвестных монадах, надо, во-первых, чтобы система типов позволяла абстрагироваться не только от типа Т, но и от контейнера (функтора) F<T>, т.е. нужны higher-kinded types. А их кроме хаскеля со скалой нигде нет практически, даже в расте нет.
EP>Есть в C++, сам же знаешь
Да, C++ и D тоже.
DM>>А во-вторых нужна поддержка синтаксиса, та же do-нотация, этого тоже нигде особо нет. Так что монады как-то в другие языки не пошли.
EP>При необходимости реализуется через макросы в C++
Здравствуйте, hi_octane, Вы писали:
_>Вот что в скале бесит (и наверное в хакселе, хоть я на нём и не пишу, но сорцы иногда читаю) — так это то что эта самая do-нотация нужна в явном виде. А в скале — так это запихнуто в for-comprehension, который спотыкается на простейших вещах (типа нормально иф в этот фор всунуть). Неужели нельзя добавить сахара в компилятор чтобы он распознавал код на монадах, и правильно его рассахаривал без do-нотации? Вроде задаче не сложнее чем та которую решает компилятор C# преобразуя функцию с async/await.
Это просто набор индексов над данными.
Вся фишка в хранении этих индексов — для не примитивных типов хранится не значение ключа, а ссылка на значение ключа в самих данных.
Но это опять тонкости реализации.
Здравствуйте, Maxim S. Shatskih, Вы писали:
MSS>Начинаю тему и приглашаю в нее тех, кто лучше меня в этом разбирается
Я плохо разбираюсь, но попробую свои 5 копеек вставить.
Начнем с того, что монада — это "паттерн" в понимании большинства программистов. Это не конструкция языка, хотя некоторые языки имеют конструкции, облегчающие использование монад.
Паттерн опирается паттерн на полиморфизм. В языках без полиморфизма монады не але.
Паттерн состоит из ЧЕТЫРЕХ элементов:
1) Полиморфный тип M<T>, тип обычно монадой и называют
2) Функция конструктор (return) T->M<T>
3) Функция связывания (bind) (M<T>, T->M<V>) -> M<V>
return и bind не произвольные, а должны подчиняться правилам:
— bind(return x, f) = f x
— bind(x, return) = x
Правило "монадного нуля"
— bind(bind(m,f), g) = bind(m, x -> bind(f(x), g))
Правило "ассоциативности bind"
4) Этот элемент отличает монадного теоретика от монадного практика.
Для практического использования монады нужна функция (run) M<T> -> T
Возможно такая функция будет не одна.
MSS>Для каких именно практических нужд пригодны монады?
Практическая польза монад — иметь один набор более-менее понятных функций для работы с объектами разной природы.
1) flatten :: M<M<T>> -> M<T> = bind(m, x->x)
2) map :: (M<T>, T->V) -> M<V> = bind(m, x-> return f(x))
3) filter :: (M<T>, T-> Maybe<V>) -> M<V>. Увы, конкретная реализация зависит от M<T> и не всегда возможна.
4) merge :: (M<T>, M<V>) -> M<(T,V>) = bind(t, x -> bind(v, y -> return (x,y)))
5) join :: (M<T>, M<V>, (T,V) -> bool) -> M<(T,V>) = filter(merge(t,v), (x,y) -> if f(x,y) then Maybe(x,y) else None). Тоже от filter зависит.
итд (лень придумывать).
При наличии в языке полиморфизма второго порядка часть функций можно вынести в библиотеки.
Но самая главня фишка, которая уже должна броситься в глаза: везде на входе и выходе M<T>.
То есть код с монадой по большей части будет выглядеть так:
bind(bind(bind(x,f),g),h)
Ни проверок, ни try\catch в таком коде не нужно. Вся "грязь" перемещается в run.
Вся сложность "инкапсулируется" в функциях f,g,h, что очень в духе Функционального Программирования.
Программистам надо писать функции типа T->M<V> и не особо думать о композиции функций. За счет частичного применения T->M<V> может иметь любое количество агрументов перед T.
Это довольно сильное преимущество, если вместо абстрактного M<T> подставить List, Maybe, Try, Parser, Future, Writer и IO монады.
MSS>и что называется монадой в языках, отличных от Хаскеля?
В других языках монадой называется все то же самое.
Но, как уже заметили, код с кучей вложенных bind смотрится криво.
Поэтому в языках, где bind записывается инфиксно (x `bind` f) монады оказываются гораздо красивее.
В хаскеле это оператор >>=, в C# это query comprehension.
Также в хаскеле есть особенность — run функция для IO встроена в рантайм.
MSS>Получается старый добрый декоратор который можно реализовать в обычных ОО языках наследованием с перекрытием пабликов.
Можно и декоратором назвать, главное bind реализовать правильно.
Самый главный монадный закон
Об этом не пишут в книгах и статьях.
Монады появляются с появления функции run.
Вы видите функцию с сигнатурой M<T> -> T или сводимую к ней путем частичного применения.
Вы задумываетесь: можно ли к типу M<T> придумать функции bind и return такие, чтобы удовлетворять монадным правилам.
Если такие функции придумываются, то поздравляю, у вас монада.
Здравствуйте, Maxim S. Shatskih, Вы писали:
MSS>Для каких именно практических нужд пригодны монады? и что называется монадой в языках, отличных от Хаскеля?
Вообще говоря, я понимаю монаду как хрень, для которой определены операции bind (то есть преобразовать объект в монаду) и flatMap, преобразующую эту монаду в последовательность других монад. Монады удобны тем, что позволяют выстраивать цепочки из нетривиальной логики достаточно элегантно. Например описывать цепочки асинхронных запросов, каллбек одного является параметром для следующего. Вместе с прокидыванием ошибок, вместе с обработки null и т.д. Основная выгода от них — они позволяют избавиться от кучи if, код в принципе выглядит стройнее, логичнее и короче. Минусы тоже есть — читаемость монад оставляет желать лучшего, понять что конкретно и как делается далеко не всегда тривиально, приходится напрягать мозги. Соответственно для многих вещей они весьма полезны. Но если можно обойтись без них, и некоторые вещи поддерживаются языком (например не Nullable типы позволяют обойтись без Maybe зачастую, функционал async await в языке позволяет строить цепочки асинхронных вызовов гораздо более читаемо, чем с помощью монад).
Соответственно для многих задач является если не панацеей, то наименьшим злом. Главное не увлекаться, во всем должно быть чувство меры.
Здравствуйте, elmal, Вы писали:
E>Вообще говоря, я понимаю монаду как хрень, для которой определены операции bind (то есть преобразовать объект в монаду) и flatMap, преобразующую эту монаду в последовательность других монад. [..]
Монада — очень простая абстракция, но как почти любая абстракция, она может быть сложной для понимания. Это же математика, просто запрограммированная, а в программировании математики остается все меньше и меньше... Вон, люди тоже не сразу поняли, что можно придумать ноль, которого как бы и нет в природе, но он оказался очень полезным. Монады тоже полезны, для того же имитационного моделирования, например.
Есть простой способ разобраться с монадами — начать изучать общецелевой язык программирования Haskell, например, по вышедшей в этом году книге "Get Programming with Haskell": https://www.manning.com/books/get-programming-with-haskell