![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
Читатели спрашивают, а на хрена нам, практикам, сдались эти ваши монады? Мы и без монад хорошо зарабатываем.
Тут можно спросить собеседников, а нужна ли им также проза? Зачем им проза, они и без прозы хорошо могут обсуждать вопросы, например, у кого какая зарплата или почём хонда у дилеров. А что они сами употребляют прозу, обсуждая эти вопросы, то ещё Мольер писал в 17-м веке, но кто нынче читает Мольера - он ни в жж, ни в твиттер не пишет, так чо.
Итак, вы можете писать что угодно на чём угодно, в смысле программирования, и монады у вас там будут независимо от того, в курсе вы или нет. Если вы в вашу функцию вставляете
Так что можно уйти в несознанку, прожить долго и счастливо и умереть дураком, и это, конечно, вариант, но не для всех. Некоторым просто любопытно.
Итак, проблемы.
Скажем, какой-нибудь метод
В нашем коде мы можем не мудрствовать особо-то, а прям так и писать:
Мы тут употребили три монады как минимум (на самом деле пять), но ощутили только одну. Это ничего.
А вот представьте теперь, что нам за каким-то хреном понадобилось составить список fof - friends-of-friends, Друзья Друзей. Ну и как мы это будем делать?
Если бы
или, эквивалентно и идиоматично, в одну строчку:
На то она и монада, что все эти действия происходят автоматически. И кстати, я не поминал раньше
Ну хорошо, а что же делать в нашем случае? Теоретически, в качестве результата мы получаем
Такое решение годится для данного конкретного случая. Но не для общего случая. Представьте такое:
Этот код распечатывает историю покупок акций; ну и представьте себе, что вы точно знаете, что вчера купили BIDU, а её в распечатке нету. Ну потому что проигнорировали. Так нельзя; надо было сразу же бросать какое-нибудь исключение, сигнализировать, что ой, не все данные собраны.
То есть смотрите - если у нас есть
Монады не коммутируют. Не существует, в общем виде, преобразования
Посмотрим, что можно сделать в случае
Хотя такое преобразование и не годится для примера с акциями, оно годится для примера с друзьями друзей. В примерах выше оно не используется, однако. На самом деле, в скальном цикле происходит вот что: и
Аналогично можно определить преобразование
Но в целом же грустный вывод такой, что композиция монад - не обязательно монада. Если не коммутируют - то не монада. А не коммутируют они... ну смотрите, в кубике Рубика повернёте верхнюю грань по часовой стрелке, переднюю по часовой стрелке, потом верхнюю против часовой стрелки, переднюю против часовой стрелки. Это, очевидно, не тождественное преобразование - не всякая группа коммутативна.
Вот этот факт, некоммутирование монад, по-моему, является одной из причин, почему программирование не является тривиальным занятием.
На эту тему много интересного можно почитать у
akukleva, у Дебасиша, у Тони Морриса (Monads do not compose), ну или, если ваш уровень хаскеля достаточен, можно прочитать 18-ю главу rwh, или вот это.
Итак, мы не можем произвольно строить монады из данных нам природой монад; но в жизни-то мы хотим соединять преобразования данных так, как нам надо; и, следовательно, надо искать какую-то не то чтобы замену монадам, а послабление. Можем же мы обойтись без чего-то?
Это послабление в компьютерной науке называется аппликативным функтором. В следующей части мы постепенно перекатимся от монад к аппликативным функторам. В компьютерной науке есть поверие, что всякая монада является аппликативным функтором. Это не так, но это ещё более другая тема.
Читатели спрашивают, а на хрена нам, практикам, сдались эти ваши монады? Мы и без монад хорошо зарабатываем.
Тут можно спросить собеседников, а нужна ли им также проза? Зачем им проза, они и без прозы хорошо могут обсуждать вопросы, например, у кого какая зарплата или почём хонда у дилеров. А что они сами употребляют прозу, обсуждая эти вопросы, то ещё Мольер писал в 17-м веке, но кто нынче читает Мольера - он ни в жж, ни в твиттер не пишет, так чо.
Итак, вы можете писать что угодно на чём угодно, в смысле программирования, и монады у вас там будут независимо от того, в курсе вы или нет. Если вы в вашу функцию вставляете
System.out.println("Mom, I'm in a function!")
, то это у вас монада. Если вы вставляете String myDrive = new File(".").getAbsolutePath().split(":")[0], то это тоже монада. Если у вас в коде
User user = DaoFactoryManager.getUserDaoFactory().getUserDao().getUserByUserId(userId); if (user != null) ...то это тоже монада, даже две.
Так что можно уйти в несознанку, прожить долго и счастливо и умереть дураком, и это, конечно, вариант, но не для всех. Некоторым просто любопытно.
Итак, проблемы.
Скажем, какой-нибудь метод
livejournal.getFriends(userid: String): Option[Set[String]]
- он возвращает или Some[Set[String]]
, или None
когда жж в дауне, т.е. довольно часто.В нашем коде мы можем не мудрствовать особо-то, а прям так и писать:
livejournal.getFriends("ivan_gandhi") match { case None => err.println("ну вот, опять Носик сломал код") case Some(friends) => { println("Это всё мои друзья:"); for (friend <- friends) println(" - " + friend) } }
Мы тут употребили три монады как минимум (на самом деле пять), но ощутили только одну. Это ничего.
А вот представьте теперь, что нам за каким-то хреном понадобилось составить список fof - friends-of-friends, Друзья Друзей. Ну и как мы это будем делать?
Если бы
livejournal
никогда не ошибался, а всегда бы возвращал Set[String]
, то мы ж могли бы выразить это наше действие одним циклом:for (f <- livejournal.getFriends("ivan_gandhi"); fof <- livejournal.getFriends(f) ) println(" - " + fof)
или, эквивалентно и идиоматично, в одну строчку:
livejournal.getFriends("ivan_gandhi").flatMap(f => livejournal.getFriends(f)).foreach(x => println(" - " + x))
На то она и монада, что все эти действия происходят автоматически. И кстати, я не поминал раньше
flatMap
.flatMap
- это map
за которым следует flatten
- сначала мы применяем map
, получая, в нашем случае, Set[Set[String]]
, а потом уже сплющиваем Set[Set[String]] → Set[String]
. Сплющивание монады Set
вещь не совсем тривиальная; могут попадаться дубликаты, их игнорируем.Ну хорошо, а что же делать в нашем случае? Теоретически, в качестве результата мы получаем
Option[Set[Option[Set[String]]]]
. Мы можем получить ошибку при выборке списка наших друзей, и при выборке списка друзей любого из наших друзей, и неоднократно. Что можно предпринять? Можно прописать решение руками, например:for (fOpt <- livejournal.getFriends("ivan_gandhi"); f <- fOpt; fofOpt <- livejournal.getFriends(f); fof <- fofOpt ) println(" - " + fof)
Такое решение годится для данного конкретного случая. Но не для общего случая. Представьте такое:
for (stockOpt <- etrade.getPortfolio("vpatryshev", "password1"); stock <- stockOpt; batchOpt <- etrade.getTrades("vpatryshev", "password1", stock); batch <- batch ) println(stock + ": " + batch.size + "@" + batch.purchasePrice)
Этот код распечатывает историю покупок акций; ну и представьте себе, что вы точно знаете, что вчера купили BIDU, а её в распечатке нету. Ну потому что проигнорировали. Так нельзя; надо было сразу же бросать какое-нибудь исключение, сигнализировать, что ой, не все данные собраны.
То есть смотрите - если у нас есть
Set[Set[T]]
, то мы это можем сплющить. А если Set[Option[Set[Option[T]]]]
, то неочевидно. Если бы мы могли это преобразовать в Set[Set[Option[Option[T]]]]
, то можно было бы отдельно плющить Set[Set[...]]
и Option[Option[T]]
. Но проблема в том, что в общем виде такого преобразования не существует. Монады не коммутируют. Не существует, в общем виде, преобразования
M[N[x]] → N[M[x]]
. Но, разумеется, в каждом конкретном случае можно какое-нибудь такое преобразование выдумать. Такие преобразования называются monad transformers по-английски; как они называются по-русски, я понятия не имею; не трансформеры же, и не трансформаторы же. Буду употреблять слово "преобразование" пока меня не поправят. Если б алгебраисты знали, какой тут катаклизм, они б подсказали - да коммутатор это, коммутатор. Но алгебраисты обычно такой фигнёй не занимаются.Посмотрим, что можно сделать в случае
Option
. Нам нужно преобразование optionT: Option[M[T]] → M[Option[T]]
, независимо от природы M
. Вот стандартное решение:def optionT[M[_], T](optionalmt: Option[M[T]]) = optionalmt match { case None => M.unit(None) // засунули None внутрь M, получив M[Option[T]] case Some(mt) => mt.map(t => Some(t)) // засунули Some() внутрь M, получив M[Option[T]] }
Хотя такое преобразование и не годится для примера с акциями, оно годится для примера с друзьями друзей. В примерах выше оно не используется, однако. На самом деле, в скальном цикле происходит вот что: и
Set[T]
, и Option[T]
наследуют от Iterable[T]
; поэтому мы фактически сплющиваем Iterable[Iterable[Iterable[Iterable[T]]]]
, получая в конце концов Iterable[T]
; дальше его можно вручную превратить в Set[T]
.Аналогично можно определить преобразование
List[M[T]] → M[List[T]]
. Но не в общем виде. Поэтому в скале и в хаскеле существуют чуть ли не библиотеки преобразований; а т.к. решение в общем случае довольно произвольно, то нельзя сказать, что это единственно правильные преобразования.Но в целом же грустный вывод такой, что композиция монад - не обязательно монада. Если не коммутируют - то не монада. А не коммутируют они... ну смотрите, в кубике Рубика повернёте верхнюю грань по часовой стрелке, переднюю по часовой стрелке, потом верхнюю против часовой стрелки, переднюю против часовой стрелки. Это, очевидно, не тождественное преобразование - не всякая группа коммутативна.
Вот этот факт, некоммутирование монад, по-моему, является одной из причин, почему программирование не является тривиальным занятием.
На эту тему много интересного можно почитать у
![[livejournal.com profile]](https://www.dreamwidth.org/img/external/lj-userinfo.gif)
Итак, мы не можем произвольно строить монады из данных нам природой монад; но в жизни-то мы хотим соединять преобразования данных так, как нам надо; и, следовательно, надо искать какую-то не то чтобы замену монадам, а послабление. Можем же мы обойтись без чего-то?
Это послабление в компьютерной науке называется аппликативным функтором. В следующей части мы постепенно перекатимся от монад к аппликативным функторам. В компьютерной науке есть поверие, что всякая монада является аппликативным функтором. Это не так, но это ещё более другая тема.