От: | omgOnoz | ||
Дата: | 18.11.14 09:49 | ||
Оценка: |
Скрытый текст | |
M>Здравствуйте, AlexRK, Вы писали: ARK>>Может кто-нибудь описать простым языком — что такое монады и зачем они нужны? M>Я могу! И отвечу еще на ряд вопросов, заданных в ветке. M>Монады в классическом виде такие страшные, потому что помимо основной идеи еще идет борьба с системой типов языка. Концепция на самом деле более универсальная и удобная. M>Теперь по порядку. Введение в весь стек с монадами в картинках. Полезно, если любите графическое представление. Плюс до функторов (включительно) я буду говорить примерно о том же. M> Значения, функции и BoxM>С примитивными (и не только) значениями все вроде бы понятно. Это обычные значения. Функции — тоже вполне классические функции, включая функции от многих аргументов:M>
M>Для введения дальнейших понятий нам нужен еще какой-нибудь контейнер, в который можно "положить" значение любого типа. Пусть это будет Box<T>. Как у него семантика — зависит от реализации. Это может быть Option, может быть Either, может быть асинхронное выполнение, observable, collection, etc... Важно только то, что это некая "обертка" поверх типа. M>Здесь же можно отметить, что у Box должен быть метод "упаковки" простого значения в контейнер. Например, так: M>
M>Это является аналогом монадного return. M> FunctorM>Просто складывать значения в Box не интересно (можно складывать Box в Box, но это тоже быстро надоест). Хочется сделать что-нибудь с Box<T>. Например, у нас Box — асинхронная задача. И хочется что-нибудь запустить после завершения результата. Другими словами, применить функцию к результату операции. Очевидно, что применение "чистой функции" к асинхронной операции может применить эту функцию не сразу, а по завершению операции. Т.е "природа" Box сохраняется.M>Можно сделать применение такой функции, например, так: M>
M>Это может быть применение функции к результату асинхронной операции, применение функции к элементу коллекции/option/either и т.д. в зависимости от семантики Box. Вот эта вот функция map называется функтором. M>Если вы внимательно посмотрите, это очень напоминает Promise. И это очень неудачный вариант предоставления функтора! Например, почти эквивалентно будет M>
M>Казалось бы, не такая и большая разница между двумя API. На самом деле очень большая, потому что мы будем обобщать применение функций дальше. Итак, следующая секция: M> ApplicativesM>Усложним задачу. Теперь нужно грузить не один ресурс, а несколько. Например, картинку с нашего сервера и какие-то данные с сервера партнеров. И после этого отображать окно интерфейса. Т.е. у нас уже естьM>
M>И теперь нам нужно бы связать операции с целевой функцией. Но ведь в предыдущей секции мы что-то подобное уже делали! Можно попробовать сделать точно так же: M>
M>Т.е. нам нужно обобщить применение функции с одного Box на несколько. Если система типов не сильно мешает, можно сделать и прямолинейно: M>
M>Вот так получается, если система типов не мешает. Мы обобщили "функтор" на несколько аргументов и получили нечто. Вот именно для получения этого нечто и сделан Applicative в haskell и аналогичных языках! В первую очередь для обобщения применения функции к нескольким Box'ам в рамках системы типов. И на практике именно так аппликативы и применяются: M>
M>В то же время это очень похоже на обычное применение функции к аргументам. Только теперь функция применяется не к самим аргументам, а к Box<arg>. M> МонадыM>Пойдем еще дальше. Нам нужно грузить некоторые данные в зависимости от уже полученных данных. Например, получив данные от партнера мы хотим еще загрузить картинку. Т.е. у нас есть функция, которая по входным данным запускает некоторый процесс и мы хотим применить ее к асинхронно загружаемым данным.M>
M>Использовать Async.apply не пройдет по типам, у нас будет Async<Async<Image>>. Но мы пока изобратаем API. Так что давайте добавим еще один метод: M>
M>Вот этот вот mmap + Box.box из первого пункта и дают монаду . Да, с формальной точки зрения это не монада (у монады другой формализм). А вот решаемая задача — именно эта. И на самом деле классичесаая монада эквивалентна вот этой "обобщенной функции" mmap. Они достаточно просто выражаются друг через друга. Зато с практической точки зрения мой вариант понятнее. И заодно показывает, как примерно монады применяются. На самом деле в таком "аппликативном стиле" они удобны даже в императивных языках (в примерах показан Async, именно с таким API я себе делал его и успешно использовал). M> Всё вместеM>Сводная табличка идей (cheat sheet):M>
M> Немного практикиM>Теперь о практической части. Да, я использовал и использую монады в императивщине. Например, в виде API из cheat sheet оно использовалось в AS3 (язык позволяет такие фокусы) для реактивного программирования и асинхронных операций. Вполне успешно и удобно. Сейчас есть "посмореть" библиотечка аппликативного реактивного программирования для scala. Вот там в силу языка аппликативы и монады гораздо больше похожи на классичесчкий вариант. Есть даже небольшой пример использования библиотечки в действии (не дописан, но смотреть можно, там уже база есть). Искать использование по ":<" и ":>" (без кавычек).M>Плюсы этих "монад" для практики. Большую часть приложения все еще можно изобразить в чистых функциях. Их удобно писать и тестировать. Какая-то небольшая часть приложения работает с "грязными" данными (в виде асинхронных операций, текущего изменяемого состояния представленного в виде Behavior, etc...). Применение функций в этих классах грязных данных тоже оттестировано (functor, applicative, monad это 4 операции на каждый класс). В результате чего места для ошибок практически нет (не надо вручную все компоненты синхронизировать, например). Ну и вид программы более декларативный. Я объявляю те же компоненты интерфейса (и текущий вид) как функцию от входных данных и состояний, а не как набор методов по копированию всего подряд из модели в вид и обратно. | |