Смотрю новый сериал FlashForward. Сюжет пересказывать не буду, желающие узнают всё необходимое, например, вот здесь. Просто соберу в кучу некоторые вопросы и теории, а потом посмотрим - что из этого оправдается, и на что будет дан ответ.
- То, в чём я не сомневаюсь.
- Никаких инопланетян, ангелов, чертей, вампиров (увы), господа бога или Святого Ника. Если другой разум - отличный от человеческого - появится в последней серии, то это будет совершенно дикий рояль в кустах. Если не в последней - тот факт, что именно эти гады виноваты в происшедшем, станет, к сожалению, очевидным.
- Никакой мистики вообще. Если бы в этом сериале была возможна мистика - она бы уже проявилась, три серии, как-никак.
- Не природный катаклизм. Слишком легко списать всё на стихию. Обесценивает всё проведённое расследование.
- Теории
- Теория темпорального эха. Пока что все персонажи считают, что, кто бы ни стоял за происшедшим, он уже всё сделал. Нажал кнопку, или что там ещё. Но, поскольку мы и так имеем дело с временной аномалией, почему не допустить, что всё произойдёт 29 марта (30-го в Европе), а имевшее место затемнение - просто эхо, откатившееся НАЗАД во времени.
- Теория шахматиста. Только у меня возникает ощущение, что наши ФБР-овцы не столько расследование ведут, сколько реагируют на ниточки, которые кто-то осознанно дёргает? И если да, то не может ли быть так, что цель затемнения - или одна из целей - это добиться того, чтобы главные герои сделали что-то или оказались где-то?
- Вопросы
- Может ли вообще наступить то будущее, которое было в видениях? Ни в жисть не поверю, что не найдётся человек, достаточно упрямый, чтобы просто назло судьбе сделать наоборот. В конце концов, для этого не много нужно. Один из ФБР-овцев говорил, если не вру, что в его видении он смотрел новости по телевизору. Что ему будет стоить выключить телевизор в соответствующее время, тем более, что оно хорошо известно. С другой стороны, некоторые, по-видимому, должны к этому времени умереть. Возможно, события подгадают таким образом, чтобы именно эти упрямцы и умерли?
- Дима Но. С одной стороны, похоже, он умрёт. С другой стороны, слишком уж активно нас подталкивают к тому, что именно это и должно случиться. И именно это, в свою очередь, напоминает о теории шахматиста - сначала Димке подсунули шерифшу, которая тут же и умерла, потом последовал звонок фиг знает откуда. Ему как бы не говорят прямым текстом (звонок по телефону не есть прямой текст, ибо его таинственность внушает сомнения), а подводят к такому убеждению. Возможно, шахматист хочет, чтобы Димка верил, что умрёт - как, скажем, в Drive профессор верил в свою обречённость, и в результате действовал так, как никогда бы не подумал действовать, будь его здоровье в порядке.
- Нулевой. Поведение этого товарища очень странно. Если он не знал о затемнении - то почему вёл себя так спокойно? Не паниковал, даже не торопился найти какое-нибудь укрытие (то, что сделал бы я). Спокойно прошёл по трибуне и удалился. Может быть, он душевнобольной? Интересно, аутисты посещают спортивные мероприятия, или им до фени? Если он знал - почему он вообще оказался на этом стадионе? Почему не отсиделся дома? Если он хотел понаблюдать за толпой - кто мешал ему выбрать местечко на крыше небоскрёба и смотреть на какую-нибудь оживлённую улицу? Может, он хотел запечатлеться на камеру? Согласуется с теорией шахматиста, но что-то в этом направлении никакого продвижения пока не видно. Может быть, он не ЗНАЛ о затемнении, но быстро всё понял? Скажем, если это талантливый физик, который зарание просчитал, что что-то подобное может быть результатом, например, включения Большого Гудронного Уклайдера? Кстати, это согласуется с теорией темпорального эха. А по телефону он тогда разговаривал со своим приятелем, который тоже был в курсе этой теории - Алё, Вовка? У тебя там такой же бардак, как и здесь? А что я тебе говорил? Вот-вот, и не зря мы те таблетки принимали, а то тоже валялись бы по земле.
- Ди Гиббонс. Разговор по телефону с Нулевым согласуется с гипотезой, что они хотели быть обнаруженными (трудно было бы представить, что ФБР не узнает рано или поздно, что во время затемнения случился разговор по телефону) - и с теорией шахматиста. С другой стороны, как сказано в предыдущем пункте, может согласовываться и с теорией темпорального эха. Что о нём знает Чарли и почему она называет Гиббонса "Ди"? Не "Дэвид Гиббонс", не "мистер Гиббонс", а именно "Ди"? Может быть, имя Гиббонса было где-нибудь написано? Умеет ли Чарли в её возрасте читать? Вероятно, да. Не является ли "Ди Гиббонс" кивком в сторону Дейва Гиббонса, соавтора "Вотчменов"? Последнее уже к загадкам сериала не относится.
- Герр Гейер. Уж больно аккуратно он их развёл. Кто знает, может быть, его освобождение было одной из целей шахматиста (буде таковой существует)?
- Сам феномен бодрствования во время затемнения. Поведение Нулевого может быть объяснено разными причинами, а вот его телефонный разговор с другим бодрствующим практически доказывает, что они сохранили сознание не случайно. Они что-то делали для этого. С другой стороны, мальчик в сомалийском флэшбеке точно был никак не связан с организаторами безобразия. У него шрам на лице - может быть, это как-то связано с его бодрствованием? Согласуется с гипотезой о психическом заболевании. Возможно, Нулевой и Ди Гиббонс страдают одним и тем же заболеванием? Может быть, они подружились на сеансе групповой терапии и когда началось затемнении, один просто рефлекторно позвонил своему единственному другу? Согласуется с тем, что поведение Гиббонса во время попытки ареста было, скажем так, неадекватным. Однако неужели подобное - явно редкое - заболевание оставит после себя лишь небольшой шрам на лбу, причём в стране, где доктор Хаус не живёт?
- Что это за фаллический символ стоял в Сомали и что за белая субстанция из него выплеснулась? Да, я осознаю, что вопрос пошлый.
- Те спецназовцы в видении Марка. Они шли за ним? Или за Стэном? Если бы Стэн находился у себя, то он был бы поблизости - Марк его, помнится, даже спрашивал, не видел ли он чего-нибудь. Он в это время сидел в сортире, но киллеры могли этого не знать.
Текущие настройки mencoder-а для конвертации видео на айфон:
mencoder источник.avi -o результат.mp4 -vf dsize=480:320:0,scale=-8:-8,harddup -oac faac -faacopts mpeg=4:object=2:raw:br=128 -of lavf -lavfopts format=mp4 -ovc x264 -x264encopts nocabac:level_idc=30:bframes=0:global_header:threads=auto:subq=5:frameref=6:partitions=all:trellis=1:chroma_me:me=umh:bitrate=500:no8x8dct
Вот так вот.
Итак, оно кончилось. 759 место, дальше не прохожу. Решил первую задачу и первую часть третьей - оно таки обломалось на large set. Вывод: с некоторыми базовыми алгоритмами у меня, всё-таки, плохо.
когда набрал в терминале "which watch".
compose-trans-0.0
Сделан по мотивам вот этого поста. Очень сильно отрефакторено и упрощено.
делал функцию "Родительский контроль" в винде? Неужели он не мог посмотреть хотя бы на макось? Как вообще в его микроскопический мозг пришла мысль, что родитель должен задавать не общее время, которое чадо проводит за компом, а конкретные часы? Не "два часа в день", а "с 17:30 до 19:30"? Или эта хуйня разрабатывалась изначально для использования в пенитенциарных учреждениях?
Почему, интересно, в макоси, да и в любом другом юниксе, никого не ебёт, какие программы юзер установит для себя лично, главное, чтобы не пытался лезть в чужие данные - а в этой куче дерьма под названием Vista по умолчанию установка софта запрещена всем не-админам? Это надо понимать как признание, что ихний выкидыш представляет собой глюк на глюке, который упадёт от первого залетевшего дятла? Какого хрена? Раньше я думал, что под админом работают только идиоты. Похоже, что в винде другого варианта вообще нет.
Ощущение такое, что виста - это не ОС, а демка. А винда Home Basic, которая шла вместе с компом - демка от демки. Повбывав бы. Уроды. Все.
Началось, как обычно, с мелочи. Дизайнеры сделали новую модель игрока, заменив устрашающий солдафонский костюм на футболку и джинсы, не менее устрашающие. В какой-то момент игрок посмотрел в небо, слегка отклонившись при этом назад. С другой позиции сразу стало видно, как автомат, висевший у игрока за спиной, прошёл у него между ногами и нагло торчит дулом аккурат из ширинки. Особо впечатлительные крестились и украдкой прикасались к томику Фрейда.
А потом пришла дверь. Обыкновенная дверь, которая просто не открывалась. До тех пор, пока её не переключали из режима физики в режим анимации, в котором она медленно открывалась, быстро захлопывалась и делала goto :begin. И надо же было именно на ней тестировать новый режим - когда работает и физика, и анимация сразу. Дверь начала исполнять танец пьяного ёжика вокруг косяка, вылетая далеко за запланированные пределы её перемещений. Испуганный девелопер выключил анимацию. Дверь пришла в себя, снялась с петель и, издевательски вращаясь вокруг вертикальной оси, улетела за горизонт.
Под конец сисадмин попросил зашедшего к нему по какой-то надобности директора налить ему чаю, потому как он сам, видите ли, до сих пор не сумел разобраться в управлении чайником.
Что-то будет завтра...
Если кому интересно, то начистить мне чайник можно здесь: http://migmit.mybrute.com
А мне всего-то хотелось сделать композицию трансформеров...
> {-# LANGUAGE GeneralizedNewtypeDeriving, RankNTypes, TypeOperators #-}Допустим, мы хотим применить к некоторой монаде несколько трансформеров. Причём, мы заранее не знаем, к какой именно монаде - но знаем, какие трансформеры. Ну, например, пусть это будут
> module MonadM where
> import Control.Monad
> newtype StateT s m x = StateT {runStateT :: s -> m (s, x)}и
> instance Monad m => Monad (StateT s m) where
> return x = StateT $ \s -> return (s, x)
> st >>= f = StateT $ \s -> runStateT st s >>= \(s', x) -> runStateT (f x) s'
> newtype ReaderT r m x = ReaderT {runReaderT :: r -> m x}Конечно, нет никакой проблемы написать трансформер-композицию.
> instance Monad m => Monad (ReaderT r m) where
> return x = ReaderT $ \r -> return x
> rt >>= f = ReaderT $ \r -> runReaderT rt r >>= \x -> runReaderT (f x) r
newtype SRT s r m x = SRT (ReaderT r (StateT s m) x)Далее, можно точно также объявить
instance Monad m => Monad (SRT s r m)и жить припеваючи.
Но очень хотелось бы сделать это единообразно, написать единый оператор композиции трансформеров. А то вдруг, скажем, мы решим поменять порядок этих трансформеров - что же тогда, инстанс переделывать?
Попробуем это сделать. Для начала, всё-таки, объявим класс для трансформеров, чтобы не всухомятку обсуждать:
class Trans t whereИ сделаем простенькую композицию:
lift :: m x -> t m x
newtype (Trans t1, Trans t2) => (t2 :. t1) m x = Compose {runCompose :: t2 (t1 m) x} deriving MonadКонтекст здесь нужен, на самом деле, только для того, чтобы все kind-ы были правильными. Позднее мы его несколько ослабим.
Далее, нужно, чтобы это был снова трансформер:
instance (Trans t1, Trans t2) => Trans (t2 :. t1) whereПока что, всё работает прекрасно. Давайте же сделаем два наших трансформера инстансами соответствующего класса, зарелизим библиотеку на Hackage и пойдём пить кофе с бубликами.
lift = Compose . lift . lift
instance Trans (StateT s) whereУпс. Получили ругань:
lift mx = StateT smx
where smx s =
do x <- mx
return (s, x)
Фикус в том, что для того, чтобы написать нашу функцию
MonadM.lhs:54:23:
Could not deduce (Monad m) from the context ()
arising from a do statement
at MonadM.lhs:54:23-29
Possible fix:
add (Monad m) to the context of the type signature for `lift'
In a stmt of a 'do' expression: x <- mx
In the expression:
do x <- mx
return (s, x)
In the definition of `smx':
smx s = do x <- mx
return (s, x)
Failed, modules loaded: none.
lift, нам нужно использовать, что аргумент засунут именно в монаду, а не во что-то ещё. Действительно нужно, это не фантазия какая-то.Попробуем пофиксить, изменив сигнатуру lift.
class Trans t whereОпять облом.
lift :: Monad m => m x -> t m x
Теперь проблема в том, что из
MonadM.lhs:49:23:
Could not deduce (Monad (t1 m)) from the context (Monad m)
arising from a use of `lift'
at MonadM.lhs:49:23-26
Possible fix:
add (Monad (t1 m)) to the context of the type signature for `lift'
or add an instance declaration for (Monad (t1 m))
In the first argument of `(.)', namely `lift'
In the second argument of `(.)', namely `lift . lift'
In the expression: Compose . lift . lift
Failed, modules loaded: none.
instance Monad m и instance Trans t не следует instance Monad (t m). Практически это всегда так - по крайней мере, это так для двух трансформеров, которые мы определили в самом начале. Но у нас нет способа убедить компилятор, что это и будет всегда так.Подход, принятый в шаблонах C++ заключается в том, чтобы забить на контекст вообще и ругаться, если он не выполняется в каждом конкретном случае. Думаю, в языке, принимающем статическую типизацию близко к сердцу, подобный вариант не имеет права на существование.
В Языке Моей Мечты(tm) я бы написал так:
class Trans t whereПосле чего я перенёс бы
lift :: Monad m => m x -> t m x
instance Monad m => Monad (t m)
instance Monad m => Monad (StateT s m) внутрь instance Trans (StateT s) и всё заработало бы. Увы, Язык Моей Мечты(tm) пока лишён важной утилиты, а именно, компилятора. Нет, интерпретатора тоже нет. Так что, этот способ тоже не сработает.Попробуем иначе. Что нам нужно, так это добавить в класс Trans какую-то функцию, которая сообщит компилятору, что происходит именно преобразование монад, а не чего-то ещё. Иначе говоря, нам нужно работать с классом Monad как с типом данных.
Попробуем это сделать.
Что вообще означает, что некоторый тип T является монадой? Это означает, что для данного типа определены несколько операций. Как учит нас теория категорий, где есть алгебраические операции (или похожие на них), стоит искать... монаду. Да-да, монаду. Правда, так как наши типы имеют не тот kind, эта монада также будет монадой на другой категории. Следовательно, имеет смысл для начала определить эту категорию:
> type (m :-> n) = forall x. m x -> n xВот они - морфизмы нашей новой категории.
Далее, опять же, теория категорий учит, что новую монаду нужно определять так: объекту p ставится в соответствие нечто вроде "множества всех выражений, составленных при помощи заданных операций из элементов p". То есть, в нашем случае подошло бы что-то в таком духе:
data MonadM p x whereЯ, однако, предпочитаю более простой и универсальный подход. Сейчас я определю тот же тип, но по-другому. Вуаля:
Term :: p x -> MonadM p x
Return :: x -> MonadM p x
Bind :: MonadM p x -> (x -> MonadM p y) -> MonadM p y
> newtype MonadM p x = MonadM {bindM :: Monad m => (p :-> m) -> m x}Это и правда то же самое. Теперь,
MonadM имеет kindи, следовательно, похож на монаду на категории типов kind-a
*MonadM> :k MonadM
MonadM :: (* -> *) -> * -> *
(* -> *). Не хватает только функций return и (>>=) для полного счастья. Сейчас мы их определим.Начнём с return. Обычно, эта функция имеет типx -> m x (так она определена в классе Monad). У нас, следовательно, тип будет
> term :: p :-> MonadM pТакую функцию написать несложно, и делается это, по существу, единственным образом:
> term px = MonadM $ \hom -> hom pxДалее, оператор
(>>=). Он у нас, по сути, уже есть. Это функция bindM. Её тип поначалу не кажется похожим на то, что нам нужно, но только потому, что у нас не хватает ещё одного важного элемента:> instance Monad (MonadM p) whereВ этом определении мы просто говорим, что правая часть, по существу, совпадает с левой, только вокруг тех штук, которые имеют тип
> return x = MonadM $ \hom -> return x
> mpx >>= f = MonadM $ \hom -> bindM mpx hom >>= \x -> bindM (f x) hom
MonadM p x добавляется некий line noise в виде bindM и hom.Теперь мы видим, что функция bindM имеет тип, который, во всяком случае, не хуже, чем то, что нам нужно:
Хорошо. Далее, то, чему не учат в Haskell-школах: конкретный объект с нужными нам операциями является ни чем иным как алгеброй над подобной монадой. В нашем случае это значит, что каждая монада является алгеброй над
*MonadM> :set -XTypeOperators -XRankNTypes
*MonadM> :t bindM :: MonadM p x -> (p :-> MonadM p) -> MonadM p x
bindM :: MonadM p x -> (p :-> MonadM p) -> MonadM p x
:: MonadM p x -> (p :-> MonadM p) -> MonadM p x
MonadM. Более конкретно, для каждой монады есть отображениеalg :: Monad m => MonadM m :-> mИменно, оно пишется так:
alg (MonadM h) = h idВ данном случае,
id имеет тип m :-> m.Как же это поможет нам решить нашу проблему? А вот как: по сути дела, указать для некоторого типа отображение alg и определить для этого же типа instance Monad - одно и то же. !. Я определю специальный тип:
> newtype Inst m = Inst {getInst :: MonadM m :-> m}и навешу конструктор на
alg следующим образом:> alg :: Monad m => Inst mДалее, идеология происходящего следующая. Если нам нужно что-то сделать с типом
> alg = Inst $ \mmx -> bindM mmx id
m, для чего требуется instance Monad, а у нас вместо него только значение inst :: Inst m, то мы проделываем всё необходимое, используя вместо m тип MonadM m (который всегда является монадой - определение только что было), а потом переносим это на тип m, используя при этом отображения term :: m :-> MonadM m и getInst inst :: MonadM m :-> m.Для того, чтобы этот перенос осуществить, нам потребуется такой класс:
class Iso t where iso :: (m :-> n) -> (n :-> m) -> (t m :-> t n)На самом деле, мне неизвестны трансформеры монад, которые не были бы ковариантны по этим монадам, так что можно сократить сигнатуру:
> class Iso t where iso :: (m :-> n) -> (t m :-> t n)
instance Iso обычно пишется несложно и бойлерплейт получится весьма небольшой.В частности, например, легко написать такое:
> infixl 1 `bindM`Заметьте, я здесь, фактически, воспроизвёл определение функции
> instance Iso MonadM where iso hom mmx = mmx `bindM` term . hom
liftM:liftM f mx = mx >>= return . fКласс трансформеров теперь определяется так:
> class Iso t => Trans t whereОбратите внимание на изменившийся контекст.
> lift :: Monad m => m x -> t m x
> liftInst :: Inst m -> Inst (t m)
В частности, теперь можно сделать трансформером композицию трансформеров.
> newtype (Iso t1, Iso t2) => (t2 :. t1) m x = Compose {runCompose :: t2 (t1 m) x} deriving MonadЗдесь я изменил контекст с
> infixr 9 :.
Trans на Iso, чтобы следующий инстанс выглядел более вменяемо:> instance (Iso t1, Iso t2) => Iso (t2 :. t1) where iso hom ttmx = Compose $ iso (iso hom) $ runCompose ttmxНу и, как я и обещал, композиция трансформеров - трансформер:
> instance (Trans t1, Trans t2) => Trans (t2 :. t1) whereНам нужно пройти от
m x к (t2 :. t1) m xОбычно мы пошли бы по маршруту m x --> t1 m x --> t2 (t1 m) x --> (t2 :. t1) m x.
Увы, если первый и последний шаги особых проблем не представляют, то второй шаг, увы, невозможен, так как t1 m не является монадой (по крайней мере, мы не можем убедить компилятор, что является). Однако, у нас есть значение alg :: Inst m, и, следовательно, также и значение liftInst alg :: Inst (t1 m). В соответствии с общей идеологией, мы сделаем второй шаг несколько более длинным, а именно, пройдём по маршруту t1 m x --> MonadM (t1 m) x --> t2 (MonadM (t1 m)) x --> t2 (t1 m) x.
Делаем:
lift = Compose . step2 . liftили, коль скоро принцип ясен,
where step2 = iso (getInst $ liftInst alg) . lift . term
> lift = Compose . iso (getInst $ liftInst alg) . lift . term. liftПока всё не слишком (надеюсь) сложно. Но сумеем ли мы сделать наши
> liftInst = isoInst . liftInst . liftInst
> where isoInst :: (Iso t1, Iso t2) => Inst (t2 (t1 m)) -> Inst ((t2 :. t1) m)
> isoInst inst = Inst $ \mmx -> Compose $ getInst inst $ iso runCompose mmx
StateT и ReaderT инстансами класса Trans? Ну, первая часть проблем не вызывает:> instance Iso (StateT s) where iso hom smx = StateT $ hom . runStateT smxЗдесь почти ничего не изменилось. Далее, нам нужно от
> instance Trans (StateT s) where
> lift mx = StateT smx
> where smx s =
> do x <- mx
> return (s, x)
Inst m перейти к Inst (StateT s m).Если бы m было монадой, то всё было бы не просто, а очень просто: достаточно было бы использовать значение alg, поскольку instance Monad m => Monad (StateT s m) у нас уже есть. Увы, m не обязательно является монадой, однако мы начинаем со значения типа Inst m! В соответствии с общей идеологией, мы пройдём по маршруту MonadM (StateT s m) --> MonadM (StateT s (MonadM m)) --> StateT s (MonadM m) --> StateT s m следующим образом:
liftInst inst = Inst $ iso (getInst inst) . getInst alg . iso (iso term)У меня лично сразу проситься вынести
alg в дополнительный параметр и написать так:> liftInst = makeLiftInst algТип для функции
> makeLiftInst :: Iso t => Inst (t (MonadM m)) -> Inst m -> Inst (t m)
> makeLiftInst alg' inst = Inst $ iso (getInst inst) . getInst alg' . iso (iso term)
makeLiftInst, признаюсь, написал не я, а компилятор. Ну, пусть будет.Аналогично пишется инстанс для ReaderT:
> instance Iso (ReaderT r) where iso hom rmx = ReaderT $ hom . runReaderT rmxОбратите внимание, что объявление функции
> instance Trans (ReaderT r) where
> lift mx = ReaderT $ const mx
> liftInst = makeLiftInst alg
liftInst совершенно одинаковое, что для StateT, что для ReaderT. Мы можем написать ещё несколько трансформеров, но везде будет то же самое. Нельзя ли его написать, например, как дефолтную реализацию в самом классе? Попробовав, получаемУвы, так не получится. Причина здесь в том, что мы для каждого конкретного
MonadM.lhs:392:30:
Could not deduce (Monad (t (MonadM m))) from the context ()
arising from a use of `alg'
at MonadM.lhs:392:30-32
Possible fix:
add (Monad (t (MonadM m))) to the context of
the type signature for `liftInst'
or add an instance declaration for (Monad (t (MonadM m)))
In the first argument of `makeLiftInst', namely `alg'
In the expression: makeLiftInst alg
In the definition of `liftInst': liftInst = makeLiftInst alg
Failed, modules loaded: none.
T определяем instance Monad m => Monad (T m) отдельно, и строчка liftInst = makeLiftInst alg как бы является обещанием, что такой инстанс определён где-то в другом месте; компилятор же это обещание тщательно проверит.На закуску - применение трансформера к монаде. Конечно, можно применять и так, но в некоторых случаях более общий подход может пригодиться:
> newtype Monad m => (t :$ m) x = Apply {runApply :: t m x}Фикус в том, что мы дописываем к значениям
> infixr 0 :$
> instance (Trans t, Monad m) => Monad (t :$ m) where
> return x = Apply $ getInst (liftInst alg) $ return x
> tmx >>= f = Apply $ getInst (liftInst alg) $ term (runApply tmx) >>= \x -> term (runApply $ f x)
tmx :: (t :$ x) x мусор вида term (runApply tmx), а обратно приходим при помощи Apply . getInst (liftInst alg). В остальном же, мы просто в правой части повторяем левую.Теперь можно писать, например, (StateT Int :. ReaderT String :$ Maybe) Char и это будет примерно (с точностью до newtype-ов) то же самое, что и (StateT Int :$ ReaderT String :$ Maybe) Char или State Int (ReaderT String (Maybe Char)).
Если кто-то вдруг захочет написать собственный трансформер MyCoolTransformer - нет проблем, пусть сделает три вещи:
1)
instance Monad m => Monad (MyCoolTransformer m)Если этого не сделать, то непонятно, почему вообще речь идёт о трансформерах монад.
2)
lift :: m x -> MyCoolTransformer m xЭто - то, для чего трансформеры монад действительно нужны.
3) Заклинание liftInst = makeLiftInst alg, которое пишется без участия мозга. Как видим, весь бойлерплейт сведён к одной строчке - что можно записывать как победу.
Маленькое замечание: здесь мы почти не пользовались тем, что речь идёт именно о монадах. Точно то же самое можно написать про трансформеры, например, стрелок. Понадобиться только а) изменить понятие морфизма, так как стрелки имеют другой kind, б) заменить два инстанса на полностью аналогичные, один для нашей "монады" (которая, если мы заменим монады на стрелки,.. останется монадой), и один для оператора применения трансформера к монадестрелке.