Miguel

Recent Entries

You are viewing the most recent 50 entries.

18th November 2009

19:17: On Vox: Текущая работа

Картинка из игры, которую мы сейчас делаем. Без комментариев.


Originally posted on migmit.vox.com

16th November 2009

01:16: On Vox: Игрушечный веб 2.0

Так как грёбаный ЖЖ не пропускает длинные посты, а бить на десять частей я не собираюсь, выложено только на Vox: клик сюды

Originally posted on migmit.vox.com

14th October 2009

01:14: On Vox: Мысли в кучу

Смотрю новый сериал FlashForward. Сюжет пересказывать не буду, желающие узнают всё необходимое, например, вот здесь. Просто соберу в кучу некоторые вопросы и теории, а потом посмотрим - что из этого оправдается, и на что будет дан ответ.

То, в чём я не сомневаюсь.

  1. Никаких инопланетян, ангелов, чертей, вампиров (увы), господа бога или Святого Ника. Если другой разум - отличный от человеческого - появится в последней серии, то это будет совершенно дикий рояль в кустах. Если не в последней - тот факт, что именно эти гады виноваты в происшедшем, станет, к сожалению, очевидным.

  2. Никакой мистики вообще. Если бы в этом сериале была возможна мистика - она бы уже проявилась, три серии, как-никак.

  3. Не природный катаклизм. Слишком легко списать всё на стихию. Обесценивает всё проведённое расследование.

Теории

  1. Теория темпорального эха. Пока что все персонажи считают, что, кто бы ни стоял за происшедшим, он уже всё сделал. Нажал кнопку, или что там ещё. Но, поскольку мы и так имеем дело с временной аномалией, почему не допустить, что всё произойдёт 29 марта (30-го в Европе), а имевшее место затемнение - просто эхо, откатившееся НАЗАД во времени.

  2. Теория шахматиста. Только у меня возникает ощущение, что наши ФБР-овцы не столько расследование ведут, сколько реагируют на ниточки, которые кто-то осознанно дёргает? И если да, то не может ли быть так, что цель затемнения - или одна из целей - это добиться того, чтобы главные герои сделали что-то или оказались где-то?

Вопросы

  1. Может ли вообще наступить то будущее, которое было в видениях? Ни в жисть не поверю, что не найдётся человек, достаточно упрямый, чтобы просто назло судьбе сделать наоборот. В конце концов, для этого не много нужно. Один из ФБР-овцев говорил, если не вру, что в его видении он смотрел новости по телевизору. Что ему будет стоить выключить телевизор в соответствующее время, тем более, что оно хорошо известно. С другой стороны, некоторые, по-видимому, должны к этому времени умереть. Возможно, события подгадают таким образом, чтобы именно эти упрямцы и умерли?

  2. Дима Но. С одной стороны, похоже, он умрёт. С другой стороны, слишком уж активно нас подталкивают к тому, что именно это и должно случиться. И именно это, в свою очередь, напоминает о теории шахматиста - сначала Димке подсунули шерифшу, которая тут же и умерла, потом последовал звонок фиг знает откуда. Ему как бы не говорят прямым текстом (звонок по телефону не есть прямой текст, ибо его таинственность внушает сомнения), а подводят к такому убеждению. Возможно, шахматист хочет, чтобы Димка верил, что умрёт - как, скажем, в Drive профессор верил в свою обречённость, и в результате действовал так, как никогда бы не подумал действовать, будь его здоровье в порядке.

  3. Нулевой. Поведение этого товарища очень странно. Если он не знал о затемнении - то почему вёл себя так спокойно? Не паниковал, даже не торопился найти какое-нибудь укрытие (то, что сделал бы я). Спокойно прошёл по трибуне и удалился. Может быть, он душевнобольной? Интересно, аутисты посещают спортивные мероприятия, или им до фени? Если он знал - почему он вообще оказался на этом стадионе? Почему не отсиделся дома? Если он хотел понаблюдать за толпой - кто мешал ему выбрать местечко на крыше небоскрёба и смотреть на какую-нибудь оживлённую улицу? Может, он хотел запечатлеться на камеру? Согласуется с теорией шахматиста, но что-то в этом направлении никакого продвижения пока не видно. Может быть, он не ЗНАЛ о затемнении, но быстро всё понял? Скажем, если это талантливый физик, который зарание просчитал, что что-то подобное может быть результатом, например, включения Большого Гудронного Уклайдера? Кстати, это согласуется с теорией темпорального эха. А по телефону он тогда разговаривал со своим приятелем, который тоже был в курсе этой теории - Алё, Вовка? У тебя там такой же бардак, как и здесь? А что я тебе говорил? Вот-вот, и не зря мы те таблетки принимали, а то тоже валялись бы по земле.

  4. Ди Гиббонс. Разговор по телефону с Нулевым согласуется с гипотезой, что они хотели быть обнаруженными (трудно было бы представить, что ФБР не узнает рано или поздно, что во время затемнения случился разговор по телефону) - и с теорией шахматиста. С другой стороны, как сказано в предыдущем пункте, может согласовываться и с теорией темпорального эха. Что о нём знает Чарли и почему она называет Гиббонса "Ди"? Не "Дэвид Гиббонс", не "мистер Гиббонс", а именно "Ди"? Может быть, имя Гиббонса было где-нибудь написано? Умеет ли Чарли в её возрасте читать? Вероятно, да. Не является ли "Ди Гиббонс" кивком в сторону Дейва Гиббонса, соавтора "Вотчменов"? Последнее уже к загадкам сериала не относится.

  5. Герр Гейер. Уж больно аккуратно он их развёл. Кто знает, может быть, его освобождение было одной из целей шахматиста (буде таковой существует)?

  6. Сам феномен бодрствования во время затемнения. Поведение Нулевого может быть объяснено разными причинами, а вот его телефонный разговор с другим бодрствующим практически доказывает, что они сохранили сознание не случайно. Они что-то делали для этого. С другой стороны, мальчик в сомалийском флэшбеке точно был никак не связан с организаторами безобразия. У него шрам на лице - может быть, это как-то связано с его бодрствованием? Согласуется с гипотезой о психическом заболевании. Возможно, Нулевой и Ди Гиббонс страдают одним и тем же заболеванием? Может быть, они подружились на сеансе групповой терапии и когда началось затемнении, один просто рефлекторно позвонил своему единственному другу? Согласуется с тем, что поведение Гиббонса во время попытки ареста было, скажем так, неадекватным. Однако неужели подобное - явно редкое - заболевание оставит после себя лишь небольшой шрам на лбу, причём в стране, где доктор Хаус не живёт?

  7. Что это за фаллический символ стоял в Сомали и что за белая субстанция из него выплеснулась? Да, я осознаю, что вопрос пошлый.
  8. Те спецназовцы в видении Марка. Они шли за ним? Или за Стэном? Если бы Стэн находился у себя, то он был бы поблизости - Марк его, помнится, даже спрашивал, не видел ли он чего-нибудь. Он в это время сидел в сортире, но киллеры могли этого не знать.

Originally posted on migmit.vox.com

7th October 2009

01:03: On Vox: Хозяйке на заметку

Текущие настройки 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

Вот так вот.

Originally posted on migmit.vox.com

26th September 2009

22:36: On Vox: Code Jam

Итак, оно кончилось. 759 место, дальше не прохожу. Решил первую задачу и первую часть третьей - оно таки обломалось на large set. Вывод: с некоторыми базовыми алгоритмами у меня, всё-таки, плохо.

Originally posted on migmit.vox.com

17th August 2009

09:26: On Vox: Первый аплоад на Hackage

compose-trans-0.0

Сделан по мотивам вот этого поста. Очень сильно отрефакторено и упрощено.

Originally posted on migmit.vox.com

10th August 2009

13:07: On Vox: Какой австралопитек

делал функцию "Родительский контроль" в винде? Неужели он не мог посмотреть хотя бы на макось? Как вообще в его микроскопический мозг пришла мысль, что родитель должен задавать не общее время, которое чадо проводит за компом, а конкретные часы? Не "два часа в день", а "с 17:30 до 19:30"? Или эта хуйня разрабатывалась изначально для использования в пенитенциарных учреждениях?

Почему, интересно, в макоси, да и в любом другом юниксе, никого не ебёт, какие программы юзер установит для себя лично, главное, чтобы не пытался лезть в чужие данные - а в этой куче дерьма под названием Vista по умолчанию установка софта запрещена всем не-админам? Это надо понимать как признание, что ихний выкидыш представляет собой глюк на глюке, который упадёт от первого залетевшего дятла? Какого хрена? Раньше я думал, что под админом работают только идиоты. Похоже, что в винде другого варианта вообще нет.

Ощущение такое, что виста - это не ОС, а демка. А винда Home Basic, которая шла вместе с компом - демка от демки. Повбывав бы. Уроды. Все.

Originally posted on migmit.vox.com

30th June 2009

22:27: On Vox: Просто забавно

Вчера всем, конечно, было не до того. Но сегодня должны были уже отойти, так что:



Originally posted on migmit.vox.com

16th June 2009

22:05: On Vox: Ну и денёк

Началось, как обычно, с мелочи. Дизайнеры сделали новую модель игрока, заменив устрашающий солдафонский костюм на футболку и джинсы, не менее устрашающие. В какой-то момент игрок посмотрел в небо, слегка отклонившись при этом назад. С другой позиции сразу стало видно, как автомат, висевший у игрока за спиной, прошёл у него между ногами и нагло торчит дулом аккурат из ширинки. Особо впечатлительные крестились и украдкой прикасались к томику Фрейда.
А потом пришла дверь. Обыкновенная дверь, которая просто не открывалась. До тех пор, пока её не переключали из режима физики в режим анимации, в котором она медленно открывалась, быстро захлопывалась и делала goto :begin. И надо же было именно на ней тестировать новый режим - когда работает и физика, и анимация сразу. Дверь начала исполнять танец пьяного ёжика вокруг косяка, вылетая далеко за запланированные пределы её перемещений. Испуганный девелопер выключил анимацию. Дверь пришла в себя, снялась с петель и, издевательски вращаясь вокруг вертикальной оси, улетела за горизонт.
Под конец сисадмин попросил зашедшего к нему по какой-то надобности директора налить ему чаю, потому как он сам, видите ли, до сих пор не сумел разобраться в управлении чайником.
Что-то будет завтра...

Originally posted on migmit.vox.com

19th May 2009

23:20: On Vox: TWIMC

Если кому интересно, то начистить мне чайник можно здесь: http://migmit.mybrute.com

Originally posted on migmit.vox.com

15th May 2009

23:42: On Vox: Классы как типы

А мне всего-то хотелось сделать композицию трансформеров...

> {-# 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
    lift = Compose . lift . lift
Пока что, всё работает прекрасно. Давайте же сделаем два наших трансформера инстансами соответствующего класса, зарелизим библиотеку на Hackage и пойдём пить кофе с бубликами.
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
*MonadM> :k MonadM
MonadM :: (* -> *) -> * -> *
и, следовательно, похож на монаду на категории типов kind-a (* -> *). Не хватает только функций 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 имеет тип, который, во всяком случае, не хуже, чем то, что нам нужно:

*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
Хорошо. Далее, то, чему не учат в Haskell-школах: конкретный объект с нужными нам операциями является ни чем иным как алгеброй над подобной монадой. В нашем случае это значит, что каждая монада является алгеброй над 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, б) заменить два инстанса на полностью аналогичные, один для нашей "монады" (которая, если мы заменим монады на стрелки,.. останется монадой), и один для оператора применения трансформера к монадестрелке.

Originally posted on migmit.vox.com

25th April 2009

20:08: On Vox: Когда-то, когда я был гораздо моложе...

я пытался освоить Лисп. И там была одна вещь, которую моё подсознательное всякий раз отвергало.

Я в принципе не мог понять, как это - результатом конструкций типа progn является результат последнего выражения. А куда же деваются результаты остальных???

Нет, разумом я понимаю: они производят некий сайд-эффект. Проблема в том, что то, что должно возвращать значение, и то, что по смыслу никакого значения возвращать не должно, а нужно только для сайд-эффекта, глазом не различается никак. Поэтому принять эту концепцию сердцем я не мог. Мне всё время казалось, что если результат этой штуковины не нужен, то её можно будет просто выкинуть, она нафиг не нужна.

Даже в Паскале сразу очевидно - здесь у нас ":=" и интересует нас возвращаемое значение; а здесь у нас никакого ":=" нет, и интересует нас сайд-эффект.

И поэтому основной частью do-синтаксиса в Хаскеле я считаю синтаксическую разницу между действием и связыванием переменной:

do action
   ...

или
do var <- expression
   ...

Originally posted on migmit.vox.com

22nd April 2009

14:51: On Vox: Игрушечный веб - 3

Я таки сделал этот чёртов ArrowLoop!

Не буду бить на несколько модулей - на винчестере у меня сейчас всё уже сильно не так, сделано довольно много изменений, так что я просто напишу, как делать ArrowLoop - используя при этом три модуля из первого постинга на эту тему.

Для начала - шапка:


> {-# LANGUAGE Arrows #-}
> module Loop where
> import Control.Arrow
> import qualified Control.Category as C
> import Control.Monad
> import Control.Monad.Fix
> import Data.Maybe
> import Data.Monoid
> import Pointed
> import Serialize
> import NetState

Здесь нет ничего особо интересного. Единственное что - я импортирую Control.Monad.Fix, потому что в одном месте мне будет удобно явно написать функцию fix.

Тип Signal из предыдущего постинга претерпел некоторые изменения - в частности, он перестал быть монадой и стал функтором:


> newtype Signal link html a = Signal ((a -> link) -> html)
> instance Functor (Signal link html) where fmap f (Signal s) = Signal $ \linkMaker -> s $ linkMaker . f

Кроме того, он является АДДИТИВНЫМ функтором - и я слегка офигел, обнаружив, что в стандартной библиотеке такого класса нет:

> class Functor f => Additive f where
>     azero :: f a
>     aplus :: f a -> f a -> f a
> instance Monoid html => Additive (Signal link html) where
>     azero = Signal $ const mempty
>     Signal s1 `aplus` Signal s2 = Signal $ \linkMaker -> s1 linkMaker `mappend` s2 linkMaker

Старый тип Signal восстанавливается из нового, а его instance Monad - из instance Additive нового:

> data SignalMonad f a = SignalMonad a (f a)
> instance Additive f => Monad (SignalMonad f) where
>     return x = SignalMonad x azero
>     SignalMonad x fx >>= h = let SignalMonad y fy = h x in SignalMonad y $ fmap (\x -> let SignalMonad y _ = h x in y) fx `aplus` fy

Теперь старый тип Signal становится SignalMonad (Signal). Получился симпатичный рефакторинг.

Однако, нам не нужен старый тип Signal. Нам нужен его вариант, имеющий не только выход, но и вход, причём (!) часть его входа может зависеть от выхода. Именно наличие такой зависимости делает возможным создание instance ArrowLoop.

Делаем:


> data SignalArrow f input output = SignalArrow {pure :: input -> output, effect :: (output -> input) -> f output}

От Kleisli(SignalMonad Signal) это отличается только тем, что вместо input в одном месте стоит (output -> input). Вот она и зависимость.

Далее - довольно стандартные инстансы. Основная идея композиции таких стрелок - если мы знаем, как возвращать сигнал из конца в начало, а нам нужно вернуть его из СЕРЕДИНЫ в начало, то мы сначала протаскиваем его в конец, а потом возвращаем в начало известным способом. Аналогично, если нужно вернуть сигнал из конца в середину - мы возвращаем его в начало, а затем протаскиваем в середину.


> instance Additive f => C.Category (SignalArrow f) where
>     id = arr id
>     sl2 . sl1 = SignalArrow {pure = pure sl2 . pure sl1, effect = e}
>         where e reaction = fmap (pure sl2) (effect sl1 $ reaction . pure sl2) `aplus` effect sl2 (pure sl1 . reaction)

Функция first требует некоторого допинывания ногами, но, как только нам удаётся удовлетворить тайпчекер - всё работает.

> instance Additive f => Arrow (SignalArrow f) where
>     arr f = SignalArrow {pure = f, effect = const azero}
>     first sl = SignalArrow {pure = first (pure sl), effect = e}
>         where e reaction =
>                   let findZ output = let (input, z) = reaction (output, z) in (output, z)
>                   in fmap findZ $ effect sl $ fst . reaction . findZ

Теперь обещанный ArrowLoop. Мы специально постарались сделать всё так, чтобы можно было его написать - ничего удивительного, что он таки написался, причём легко.

> instance Additive f => ArrowLoop (SignalArrow f) where
>     loop sl = SignalArrow {pure = \input -> let (output, z) = pure sl (input, z) in output, effect = e}
>         where e reaction = fmap fst $ effect sl $ first reaction

Наконец, самое забавное. ArrowChoice.

Фишка в том, что ArrowChoice даёт нам возможность, в зависимости от приходящих сигналов, рендерить разные части виджета. При этом мы не хотим, чтобы сигнал, пройдя через виджет и вернувшись назад по какому-то циклу, поменял выбор той части, которая должна рендериться. Смена отображаемого куска должна происходить только между загрузками страницы, но не во время. Гарантировать это статически мы не можем никак. Поэтому я сознательно допускаю возможность, что в этом месте вычисление упадёт с ошибкой. Оно не должно падать - и не будет, если страница написана нормально.


> instance Additive f => ArrowChoice (SignalArrow f) where
>     left sl = SignalArrow {pure = left $ pure sl, effect = e}
>         where e reaction =
>                   case fix $ reaction . left (pure sl) of
>                     Left _ -> fmap Left $ effect sl $ \output -> let Left input = reaction $ Left output in input
>                     Right _ -> azero

Собираем всё это вместе, не забыв, как обычно, добавить состояние:

> type Link = String
> type Html = String
> type Widget = NetState (SignalArrow (Signal Link Html))

На вход всей страницы всегда подаётся (), а локальное состояние зачитывается из пришедшего от пользователя URL. Выход страницы игнорируется - поэтому, обратной связи, фактически, не будет - точнее, вместо функции она будет константой:

> renderPage :: Widget () output -> Maybe Link -> Html
> renderPage (NetState sl) ml =
>     let Signal render = effect sl $ const ((), maybe point readSer ml)
>     in render $ \(_, local) -> writeSer local

Теперь нужны label, link и state - почти такие же, как в прошлом постинге.

Для начала - label. Выход label - всегда (), поэтому обратная связь не может быть ничем, кроме константы; нас интересует, следовательно, её единственное значение:


> label :: Widget String ()
> label = NetState $ SignalArrow {pure = const ((),()), effect = \reaction -> Signal $ const $ fst (reaction ((),())) ++ "\n"}

Вход link - всегда (), поэтому обратная связь может быть только const (). Поэтому, мы её вообще проигнорируем.

> link :: String -> Widget () Bool
> link caption = NetState $ SignalArrow {pure = const (False, ()), effect = const $ Signal $ \linkMaker -> caption ++ " <" ++ linkMaker (True, ()) ++ ">\n"}

Ну и, наконец, state. State не отображается никак, а потому не интересуется обратной связью.

> state :: Serialize local => local -> Widget (local -> local) local
> state initial = NetState $ SignalArrow {pure = p, effect = const azero}
>     where p (f, ml) = let l = fromMaybe initial ml in (l, Just $ f l)

Готово. Попробуем, чтобы убедиться, что старые примеры продолжают работать:

> test1 =
>     proc () ->
>         do clicked <- link "+" -< ()
>            number <- state (0 :: Integer) -< if clicked then (+ 1) else id
>            label -< show number
>            link "refresh" -< ()

Загружаем в GHCi:

*Loop> putStr $ renderPage test1 $ Nothing
+ <Y1,>
0
refresh <Y0,>
*Loop> putStr $ renderPage test1 $ Just "Y1,"
+ <Y2,>
1
refresh <Y1,>

Теперь убедимся, что новые фокусы тоже работают:

> test5 =
>     proc () ->
>         do rec {label -< show number;
>                 number <- state (0 :: Integer) -< if clicked then (+ 1) else id;
>                 clicked <- link "+1" -< ()}
>            link "refresh" -< ()

В этом примере всё почти также, как и в test1 - только ссылка, изменяющая счётчик, расположена ПОСЛЕ самого счётчика. Это было невозможно со старой реализацией, зато с новой:

*Loop> putStr $ renderPage test5 $ Nothing
0
+1 <Y1,>
refresh <Y0,>
*Loop> putStr $ renderPage test5 $ Just "Y1,"
1
+1 <Y2,>
refresh <Y1,>

Работает, однако. Чувствую, пора из игрушечного фреймворка делать полноразмерный.

Последнее замечание: виджет-хамелеон, который упоминался в прошлый раз, по-прежнему не делается. И я не уверен, что его удастся сделать более-менее разумным образом.

Originally posted on migmit.vox.com

14th April 2009

22:26: On Vox: Офигительно

Довольно банальная завязка - американка, вышедшая замуж за англичанина, приезжает в его дом и знакомится с его семьёй, явно её не одобряющей - превратилась в классно сыгранный, классно поставленный фильм с классным саундтреком. Рекомендую - Easy Virtue, или "Лёгкое поведение". Кстати, в переводе, вроде бы, идёт в наших кинотеатрах прямо сейчас.




Originally posted on migmit.vox.com

7th April 2009

02:46: On Vox: Игрушечный веб - 2
Продолжение; начало здесь
Теперь - основное: собственно, виджеты.
> {-# LANGUAGE Arrows #-}
> module HTML where
> import Control.Arrow
Этот модуль реально подключается только ради стрелок Клейсли (как мы помним, каждая монада даёт стрелку - вот, это они и есть).
> import Data.Maybe
> import Data.Monoid
Ну, куда же без моноидов...
> import NetState
> import Pointed
> import Serialize
Три предыдущих модуля. Пригодится.
Для начала мы соорудим монаду, как первое приближение к виджетам. Наш "недовиджет" будет посылать некоторый сигнал; кроме того, он будет содержать произвольное количество ссылок. Клик по каждой ссылке меняет состояния, потенциально, всех остальных виджетов на странице. Но как именно он их меняет? Только при помощи изменения выходного сигнала данного виджета - это единственный способ для нашего виджета повлиять на других. Поэтому, каждая ссылка а) определяет новый выходной сигнал, и б) содержит новые состояния всех виджетов на странице, причём б) определяется по а). Вот эту самую функцию, определяющую б) (а точнее, сразу URL, который надо запихнуть в ссылку) по а), мы передадим "недовиджету" как параметр:
> data Signal link html a = Signal a ((a -> link) -> html)
Теперь надо превратить это дело в монаду. Виджет "return" не будет отображаться вообще, он будет лишь выдавать сигнал на выход; для отображения связки двух виджетов мы сначала отображаем один из них, затем второй:
> instance Monoid html => Monad (Signal link html) where
>     return x = Signal x $ const mempty
>     Signal x render1 >>= f =
>         let Signal y render2 = f x
>             render linkMaker = render1 (\x -> let Signal y _ = f x in linkMaker y) `mappend` render2 linkMaker
>         in Signal y render
Наши URL-ы будут просто строками; выходной HTML - тоже всего лишь строкой:
> type Html = String
> type Link = String
Теперь мы хотим добавить к нашим виджетам состояние. У нас уже есть способ это сделать, но он работает со стрелками, а не с монадами. Вот тут и нужны стрелки Клейсли:
> type Widget = NetState (Kleisli (Signal Link Html))
Сразу соорудим функцию для показа наших виджетов (а вся страница, разумеется, есть один большой виджет). Нам нужно а) десериализовать состояние из пришедшего URL-а; б) передать на вход виджета... ничего не передавать, поэтому входной тип должен быть (), в) при порождении каждой ссылки из глобального состояния страницы просто сериализовать это самое глобальное состояние. Делаем:
> renderPage :: Widget () output -> Maybe Link -> Html
> renderPage (NetState (Kleisli widget)) ml =
>     let Signal _ render = widget ((), maybe point readSer ml)
>     in render $ \(_, local) -> writeSer local
Теперь нам нужны три базовых "кирпичика": виджет, отображающий текст, виджет, отображающий ссылку, и виджет, хранящий некое состояние. Пишутся они достаточно элементарно, единственная тонкость: выходной сигнал виджета-ссылки - это Bool: либо по ссылке кликнули, либо нет.
> label :: Widget String ()
> label = NetState $ Kleisli $ \(text, _) -> Signal ((),()) $ const $ text ++ "\n"
> link :: String -> Widget () Bool
> link caption = NetState $ Kleisli $ const $ Signal (False, ()) $ \linkMaker -> caption ++ " <" ++ linkMaker (True, ()) ++ ">\n"
> state :: (Serialize local) => local -> Widget (local -> local) local
> state initial = NetState $ Kleisli $ \(f, mx) -> let x = fromMaybe initial mx in Signal (x, Just $ f x) $ const ""
Готово. Теперь можно обозвать это умным словом "фреймворк". Нет, правда, готово.
Проверим. Первый тест - страница, содержащая две ссылки и поле, отображающее число. Нажатие на первую ссылку увеличивает число на 1; нажатие на вторую - рефрешит страницу:
> test1 =
>     proc () ->
>         do clicked <- link "+" -< ()
>            number <- state (0 :: Integer) -< if clicked then (+ 1) else id
>            label -< show number
>            link "refresh" -< ()
Проверяем в GHCi:
*HTML> putStr $ renderPage test1 $ Nothing
+ <Y1,>
0
refresh <Y0,>
*HTML> putStr $ renderPage test1 $ Just "Y1,"
+ <Y2,>
1
refresh <Y1,>
*HTML> putStr $ renderPage test1 $ Just "Y2,"
+ <Y3,>
2
refresh <Y2,>
В первый раз мы подаём на вход Nothing; затем мы каждый раз подаём на вход URL из той ссылки, по которой мы, вроде как, кликнули.
Второй тест - снова две ссылки и число, но на сей раз вторая ссылка уменьшает число на 1:
> test2 =
>     proc () ->
>         do increase <- link "+" -< ()
>            decrease <- link "-" -< ()
>            number <- state (0 :: Integer) -< \n -> n + (if increase then 1 else 0) - (if decrease then 1 else 0)
>            label -< show number
Проверяем:
*HTML> putStr $ renderPage test2 $ Nothing
+ <Y1,>
- <Y-1,>
0
*HTML> putStr $ renderPage test2 $ Just "Y-1,"
+ <Y0,>
- <Y-2,>
-1
*HTML> putStr $ renderPage test2 $ Just "Y-2,"
+ <Y-1,>
- <Y-3,>
-2
Работает.
Третий пример: размещаем на странице ДВА виджета из первого примера. По идее, они должны работать независимо:
> test3 =
>     proc () ->
>         do test2 -< ()
>            test2 -< ()
И тестируем:
*HTML> putStr $ renderPage test3 $ Nothing
+ <Y1,Y0,>
- <Y-1,Y0,>
0
+ <Y0,Y1,>
- <Y0,Y-1,>
0
*HTML> putStr $ renderPage test3 $ Just "Y1,Y0,"
+ <Y2,Y0,>
- <Y0,Y0,>
1
+ <Y1,Y1,>
- <Y1,Y-1,>
0
*HTML> putStr $ renderPage test3 $ Just "Y2,Y0,"
+ <Y3,Y0,>
- <Y1,Y0,>
2
+ <Y2,Y1,>
- <Y2,Y-1,>
0
*HTML> putStr $ renderPage test3 $ Just "Y2,Y1,"
+ <Y3,Y1,>
- <Y1,Y1,>
2
+ <Y2,Y2,>
- <Y2,Y0,>
1
*HTML> putStr $ renderPage test3 $ Just "Y1,Y1,"
+ <Y2,Y1,>
- <Y0,Y1,>
1
+ <Y1,Y2,>
- <Y1,Y0,>
1
И опять работает.
Четвёртый пример: своего рода "визард" с двумя страницами, с кнопкой для переключения. На каждой странице мы разместим виджет из второго примера:
> test4 =
>     proc () ->
>         do switch <- link "switch" -< ()
>            displayFirst <- state True -< if switch then not else id
>            if displayFirst
>               then do label -< "first page"
>                       test2 -< ()
>               else do label -< "second page"
>                       test2 -< ()
GHCi-сессия:
*HTML> putStr $ renderPage test4 $ Nothing
switch <YnY0,N>
first page
+ <YyY1,N>
- <YyY-1,N>
0
*HTML> putStr $ renderPage test4 $ Just "YyY1,N"
switch <YnY1,N>
first page
+ <YyY2,N>
- <YyY0,N>
1
*HTML> putStr $ renderPage test4 $ Just "YnY1,N"
switch <YyY1,Y0,>
second page
+ <YnY1,Y1,>
- <YnY1,Y-1,>
0
*HTML> putStr $ renderPage test4 $ Just "YnY1,Y-1,"
switch <YyY1,Y-1,>
second page
+ <YnY1,Y0,>
- <YnY1,Y-2,>
-1
*HTML> putStr $ renderPage test4 $ Just "YyY1,Y-1,"
switch <YnY1,Y-1,>
first page
+ <YyY2,Y-1,>
- <YyY0,Y-1,>
1
Чего здесь не хватает?
Во-первых, каждый виджет может влиять лишь на те виджеты, которые идут после него. Для влияния "назад" нам понадобился бы instance ArrowLoop Widget - который мы автоматически получили бы, если бы сообразили instance MonadFix Signal. Тогда можно было бы написать, скажем,
> test5 =
>     proc () ->
>         do rec {label -< show number;
>                 number <- state (0 :: Integer) -< if clicked then (+ 1) else id;
>                 clicked <- link "+1" -< ()}
>            returnA -< ()
Увы, с текущей реализацией Signal это, похоже, невозможно.
Другая фишка, которую мне лично очень хотелось бы иметь - это "виджет-хамелеон", который может получить на вход другой виджет и вести себя как он, до тех пор, пока не получит новый виджет, и станет вести себя уже как он. Подобная вещь была в фуджетах; как это счастье реализовать, я лично пока не очень представляю.
На сегодня всё, спасибо за внимание.

Originally posted on migmit.vox.com

02:32: On Vox: Игрушечный веб - 1
Как-то странно получается. Я активно не люблю стрелки (имеются в виду, естественно, хаскельные Arrows), и, тем не менее, постоянно их сочиняю, как правило, применительно к вебу. На этот раз речь пойдёт о задачке, которую несколько невнятно сформулировал [info]mr_aleph в своём посте #rocket web-science.
Речь о том, чтобы сымитировать десктопное приложение в вебе, не прибегая к помощи джаваскрипта и не храня ничего на сервере. Для простоты мы ограничимся выводом текста и кнопками - в роли которых у нас будут выступать ссылки. Задумка в том, чтобы клик по ссылке работал как нажатие кнопки, меняя состояние виджетов на странице (т.е., в основном, меняя отображаемые надписи). При этом, состояние виджетов, не имеющих отношения к этой кнопке, должно, естественно, сохраняться. Отсюда вытекает, что в каждой ссылке должно быть прописано состояние всех виджетов вообще, которые есть на странице - и в то же время мы хотим писать виджеты, содержащие ссылки, не зная заранее, что на странице будет ещё.
Итак, в бой. Задача прикручивания всего этого к какому-нибудь веб-серверу (например, happstack-у) мне представляется чисто технической, а потому неинтересной. Мы будем использовать упрощённый формат: выводить по одной надписи или ссылке на строчку и вручную запускать нашу "страницу", передавая ей в качестве параметра ту "ссылку", на которой мы, вроде как, кликнули. Ссылки будем выводит так: caption <URL>.
Первый модуль, который я использую, появляется по одной-единственной причине: мне нужно, чтобы страница, в которую мы специально не запихивали никакое состояние (как бывает, когда страница вызывается в первый раз), всё-таки какое-то состояние имела. Я подумывал использовать в качестве состояния каждого виджета Maybe что-то-там, но решил, что проще будет использовать специальный класс. Уже потом я сообразил, что Maybe ... - это СВОБОДНЫЕ алгебры над монадой Maybe, а подобный класс - это ВСЕ алгебры над этой же монадой:
> module Pointed where
> class Pointed l where point :: l
> instance Pointed () where point = ()
> instance Pointed (Maybe a) where point = Nothing
> instance (Pointed a, Pointed b) => Pointed (a, b) where point = (point, point)
Тут, в общем-то, всё понятно. Кстати говоря, в языке моей мечты класс Pointed будет единственным классом вообще.
Второй модуль необходим для сериализации/десериализации состояний. Собственно, никто не мешает использовать стандартную комбинацию (Show a, Read a), но при этом получаются настолько огромные выражения, что на них просто неприятно смотреть.
Здесь мы используем довольно стандартный трюк, слегка напоминающий "разностные списки". То, что нам нужно - это функции a -> String и String -> a. Подобные штуки, увы, плохо комбинируются; поэтому, мы соорудим ПРЕОБРАЗОВАТЕЛИ таких функций - и вот они уже комбинируются хорошо: всё, что нам нужно - это, в общем-то, сериализовать пару, умея сериализовать её компоненты; это делается банальной композицией соответствующих преобразователей:
> module Serialize where
> class Serialize a where
>     serialize :: (b -> String) -> (a, b) -> String
>     deserialize :: (String -> b) -> String -> (a, b)
Коль скоро мы хотим, всё-таки, именно сериализации и десериализации - нам понадобятся соответствующие функции
> writeSer :: Serialize a => a -> String
> writeSer x = serialize (const "") (x, ())
> readSer :: Serialize a => String -> a
> readSer s = let (x, ()) = deserialize (const ()) s in x
Ключевая идея - в том, что мы худо-бедно знаем, как сериализовать (), а, значит, можем (при помощи нашего преобразователя) сериализовать пару, где () будет на втором месте - а это то же самое, что сериализовать первый компонент пары.
Кстати, это наше знание, как сериализовать () надо бы оформить:
> instance Serialize () where
>     serialize f (_, y) = f y
>     deserialize f s = ((), f s)
Далее, обещанная сериализация пары:
> instance (Serialize a, Serialize b) => Serialize (a, b) where
>     serialize f ((x, y), z) = serialize (serialize f) (x, (y, z))
>     deserialize f s = let (x, (y, z)) = deserialize (deserialize f) s in ((x, y), z)
Ну и ещё несколько инстансов, шоб було; они все довольно очевидные:
> instance Serialize Integer where
>     serialize f (n, y) = show n ++ "," ++ f y
>     deserialize f s = let (s1, ',':s2) = break (',' ==) s in (read s1, f s2)
> instance Serialize a => Serialize (Maybe a) where
>     serialize f (Nothing, y) = 'N' : f y
>     serialize f (Just x, y) = 'Y' : serialize f (x, y)
>     deserialize f ('N':s) = (Nothing, f s)
>     deserialize f ('Y':s) = let (x, y) = deserialize f s in (Just x, y)
> instance Serialize Bool where
>     serialize f (True, x) = 'y' : f x
>     serialize f (False, x) = 'n' : f x
>     deserialize f ('y':s) = (True, f s)
>     deserialize f ('n':s) = (False, f s)
OK, далее начинается интересное. Допустим, у нас уже есть некая стрелка, и мы хотим добавить в неё состояние, причём достаточно произвольного типа. При комбинировании стрелок соответствующие состояния тоже должны комбинироваться. Стрелка имеет некоторое состояние и в процессе вычисления ИЗМЕНЯЕТ его.
> {-# LANGUAGE ExistentialQuantification, Arrows #-}
Коли наше состояние должно быть различных типов - не обойтись без forall; коли мы говорим о стрелках - не обойтись без специального синтаксиса для них.
> import Control.Arrow
> import qualified Control.Category as C
> import Pointed
> import Serialize
Первые два импорта стандартны для программ, определяющих свои стрелки; последние два - подключаем два предыдущих модуля, так как состояние у нас обязательно будет а) сериализуемое, и б) имеющее значение по умолчанию. Модуль Control.Category подключается с префиксом, так как в нём есть функция id, конфликтящая со стандартной.
> data NetState a input output = forall local. (Serialize local, Pointed local) => NetState (a (input, local) (output, local))
И вот он, самый смак. Определение почти очевидное; вместо двух наших классов можно использовать любой класс X, лишь бы для него был определён instance (X a, X b) => X (a, b). Однако, как только оно написано, определения стрелочных операций получаются моментально:
> instance Arrow a => C.Category (NetState a) where
>     id = arr id
>     NetState ns2 . NetState ns1 = NetState ns
>         where ns =
>                   proc (input, (local1, local2)) ->
>                       do (middle, l1) <- ns1 -< (input, local1)
>                          (output, l2) <- ns2 -< (middle, local2)
>                          returnA -< (output, (l1, l2))
> instance Arrow a => Arrow (NetState a) where
>     arr f = NetState $ proc (input, _) -> returnA -< (f input, ())
>     first (NetState ns) = NetState ns'
>         where ns' =
>                   proc ((input, z), local) ->
>                       do (output, l) <- ns -< (input, local)
>                          returnA -< ((output, z), l)
> instance ArrowChoice a => ArrowChoice (NetState a) where
>     left (NetState ns) = NetState ns'
>         where ns' =
>                   proc (inputOrZ, local) ->
>                       case inputOrZ of
>                         Left input ->
>                             do (output, l) <- ns -< (input, local)
>                                returnA -< (Left output, l)
>                         Right z -> returnA -< (Right z, local)
> instance ArrowLoop a => ArrowLoop (NetState a) where
>     loop (NetState ns) = NetState ns'
>         where ns' =
>                   proc (input, local) ->
>                       do rec ((output, z), l) <- ns -< ((input, z), local)
>                          returnA -< (output, l)
Здесь почти не о чем говорить. Состояние композиции двух стрелок есть пара из состояния первой и состояния второй из них. Обратите внимание, что для instance C.Category (NetState a) недостаточно C.Category a, требуется Arrow.
Продолжение следует.

Originally posted on migmit.vox.com

22nd March 2009

02:29: On Vox: О флагах

Продолжается процесс выбора логотипа для Хаскеля. У меня потихоньку закрадываются сомнения в том, что этот процесс является сходящимся.

А вот я сегодня увидел где-то флаг Чехии:

И понял, что это почти наш флаг:


Originally posted on migmit.vox.com

17th March 2009

16:05: On Vox: Взаимность

Когда в Haskell-cafe обсуждают теоркат - это нормально. Теперь в рассылке по теоркату начали обсуждать Haskell. И весьма активно, приводя примеры кода.

Originally posted on migmit.vox.com

16th February 2009

20:29: On Vox: Интересно

в хохмагазинах можно купить компас со стрелкой, намагниченной поперёк?

Originally posted on migmit.vox.com

9th February 2009

16:25: On Vox: Просамоцитируюсь

Из комментов к посту про платную/бесплатную медицину на ЛОРе:

> Сервис - ничуть не хуже коммерческого.

Ты пробовал когда-нибудь попадать в больницу? Обычную, бюджетную?

Меня бог пока миловал. Зато некоторых родственников я там посещал (и сопровождал).

Так вот. Сначала, приехав, ты несколько часов лежишь на каталке в приёмном покое. Вокруг тебя толчётся человек двадцать больных с разнообразными неизвестными пока болячками. Медсестёр две-три, и на вопрос "сестра, когда же моя очередь" они злобно огрызаются, ибо заебались уже всем повторять, что не знают. Вполне возможна ситуация, когда кто-нибудь подойдёт и вколет тебе что-нибудь, проигнорировав твои робкие вопросы "а что это" и "от чего это" - лежи потом, и думай, не перепутали ли тебя с другим больным. Каталка неудобная, где сортир - неизвестно, поесть не принесут.

Потом ты попадаешь в палату, где лежат ещё четыре-пять человек. Один из них непрерывно орёт. Не потому, что ему больно, нет. Просто он сумасшедший. Поэтому никто никаких мер, чтобы он не орал, принимать не будет. Если ты можешь ходить, то ещё один сосед будет с частотой раз в пять минут просить тебя позвать сестру, потому как сам он ходить не может. Если ты не можешь ходить, то никто из соседей для тебя сестру не позовёт. А если позовёт - будет хуже, сестра придёт злобная и уставшая от постоянной беготни по больным. Вонища в палате будет стоять страшная, а если ты попытаешься проветрить помещение, на тебя заорут в несколько глоток, что ты хочешь, чтобы они все поумирали от простуды - что, кстати, недалеко от истины.

Поесть ты сможешь; если ты лежачий - тебе принесут. Принесут откровенные помои. Не торопись вставать на ноги - в столовой то же самое. Если у тебя нет родственников, которые готовы каждый день носить тебе еду - твои дела плохи. Именно каждый день, потому что холодильника нет и не будет. Об элементарной вещи типа электрической розетки в палате (хотя бы мобильник зарядить) - не мечтай. И не оставляй мобилу без внимания - в больницах воруют, и много.

Если ты совсем плох и вообще ничего не можешь - больница сделает тебе ещё хуже. Сталкивался со случаем, когда больной, простите, обосрался и лежал в собственном дерьме - он физически был не в состоянии что-то исправить, а сестра подходить не торопилась.

Не думай, что, заплатив медсестре, ты сможешь избавиться хотя бы от одного недостатка из указанных. Платят ВСЕ. Ну, или, по крайней мере, многие. Они физически не в состоянии обеспечить более-менее полноценный уход каждому.

Излишне говорить, что в КОММЕРЧЕСКИХ больницах ничего этого нет. Исключением может стать разве что качество еды - оно и в платных больницах бывает не очень (хотя и получше), но зато холодильник там, как правило, есть.

А теперь учти: если ты вызовешь бесплатную скорую, и она обнаружит, что тебе необходима госпитализация - тебя повезут в бесплатную же больницу. В ту, к которой эта скорая приписана. Если ты вызываешь платную скорую - тебя повезут в ту больницу, в которую ты захочешь сам; если твои предпочтения не столь определённы - врач обзвонит больницы, которые тебе подходят, и выяснит, где есть места.

Miguel

Originally posted on migmit.vox.com

5th February 2009

00:51: On Vox: Объясните, а?

Вот есть хаскельный код (пример упрощён до предела):

data P a = P a (forall b. b -> P (a, b))
Нормально компилится, если указать прагму LANGUAGE RankNTypes в GHC или ключик -98 в Hugs-е.
Можно написать несколько "генераторов" для такого P:
sameValue :: a -> P a
sameValue x = P x (\y -> sameValue (x, y))

firstRest :: a -> a -> P a
firstRest x y = P x (\z -> sameValue (y, z))

switching :: a -> a -> P a
switching x y = P x (\z -> switching (y, z) (x, z))
Как сделать это на C++???
Я попробовал, моего плюс-фу не хватило. Светилы, если вы есть, можете подсказать?
Хочется что-то вроде этого:

#include <map>
template <class A> class P {
  A car;
  template <class B> virtual P<std::pair<A,B> > cdr (B) = 0;
};

Не заработает, ибо template и virtual вместе не живут. Убрать virtual нельзя - класс определяется как абстрактный, реализация функции cdr будет разной (см. выше), но эту разницу надо скрыть "под капотом", указывая везде базовый класс.
Как?

Originally posted on migmit.vox.com

18th January 2009

03:29: On Vox: Дивное

Сюрьреализьмом попахивает.

Покрадено отсюда.

Originally posted on migmit.vox.com

13th January 2009

23:04: On Vox: Вау

Только что обнаружил. Офигел.
http://goosh.org/

Originally posted on migmit.vox.com

25th December 2008

00:33: On Vox: Попробовал написать

длинный пост про хаскель. После усушки и утруски осталось следующее:

Бездушное программирование - это программирование без блоков do.

Причём я даже не уверен, что не слямзил это где-нибудь.

Originally posted on migmit.vox.com

17th December 2008

03:08: On Vox: И ещё перевод

Оригинал здесь: http://www.aegisub.net/2008/12/if-programming-languages-were-religions.html

Если бы языки программирования были религиозными учениями.
Примечание переводчика: в haskell-cafe уже задали вопрос "Что значит 'если'?"

Идея почерпнута из известного текста "Если бы языки программирования были автомобилями".

C - это иудаизм. Он очень стар, содержит большое количество ограничений, но большая часть общества знакома с его законами и уважает их. Проблема в том, что перейти в эту веру нельзя - вы либо следуете ей с рождения, либо находите её идиотской. Кстати, когда что-то идёт не так, очень многие сваливают вину на C (соответственно, евреев).

Java - ортодоксальное христианство. Теоретически, Java основана на C, однако она отменяет такое количество старых правил, что совершенно непохожа на оригинал. Вместо них появляются новые правила, которые последователи этой веры искренне считают много лучшими, чем старые. При этом они не только убеждены, что их язык - лучший в мире, но готовы сжигать на костре тех, кто с ними не согласен.

PHP - неформальное христианство (в оригинале Cafeteria Christianity - не нашёл перевода). Оно борется с Java за рынок веб-приложений. Заимствует некоторые концепции из C и Java, но только те, которые ему нравятся. Возможно, оно не настолько последовательно, как другие языки, но по крайней мере даёт много свободы и, ВРОДЕ КАК, сохраняет основную идею. Да, и ещё: понятие "goto hell" там отброшено.

С++ - ислам. Основан на C, но не только сохраняет все его запреты, а ещё и добавляет поверх них большую кучу собственных. Он настолько разнообразен, что из него может проистекать всё, что угодно - от чудовищных зверств до прекрасных произведений искусства. Последователи этой религии убеждены, что C++ - универсальный язык для всего, оптимальный в любой ситуации, и могут разозлиться, если вы не согласитесь. Наиболее радикальные адепты будут угрожать вам физической расправой, если вы нелестно выскажетесь о самом языке или его создателе.

C# - мормонизм. Поначалу кажется, что перед нами та же Java, но, присмотревшись получше, вы замечаете, что эта религия контролируется единой корпорацией (по мнению адептов Java представляющей собой воплощение зла на земле), а многие теологические положения сильно отличаются. Вам может даже показаться, что это неплохой вариант, если бы только последователи Java-религии перестали унижать вас за использование C#.

Лисп - дзен-буддизм. Никакого синтаксиса, никакой централизации вероучения, никакого поклонения святым. Вся вселенная в вашем распоряжении - только просветитесь настолько, чтобы её достичь. Одни говорят, что это вообще не язык, другие - что это единственный язык, имеющий смысл.

Haskell (ну вот, добрались!) - таоизм. Он настолько не похож на другие языки, что далеко не все понимают, как с его помощью вообще можно сделать что-то работающее. Его адепты верят, что в их руках - подлинный ключ к мудрости, но эта мудрость недоступна большинству смертных.

Эрланг - индуизм. Ещё один странный язык, непонятно как применимый на практике, но, в отличие от других современных языков, основан на идее большого числа одновременно существующих богов.

Perl - религия вуду. Беспорядочный набор невнятных заклинаний, которые требуют кровь козлёнка, необратимо разлагающих твою душу. Часто используется, если шеф приносит новую срочную работу в девять вечера в пятницу.

Lua - викканство. Пантеистический язык, который можно легко приспособить к особенностям местной культуры, где бы вы ни оказались. Он предоставляет много свободы, включая такие действия, которые в более традиционных религиях считались бы магическими. Сильно связан с луной. (Примечание переводчика: Lua - "луна" на португальском).

Ruby - неоязычество. Смесь разнородных представлений и верований, собранных в кучу, отдалённо напоминающую язык программирования. Число его адептов быстро растёт, и, хотя многие относятся к ним с подозрением, они, как правило, не хотят ничего дурного.

Python - атеизм. Прост, нетребователен, не нуждается в чём-либо кроме здравого смысла. Многие последователи утверждают, что освободились от давления других языков и снова открыли для себя удовольствие от программирования. Есть также мнение, что Python - что-то вроде псевдокода.

COBOL - язычество. Когда-то он был весьма распространён и существенен; сейчас - практически мёртв (и слава богу). Хотя его ритуалы порой пугающи, до сих пор находятся люди, настаивающие на его сохранении.

APL - сайентология. Хотя куча народу утвержают, что следуют этой вере, вам постоянно кажется, что это какой-то большой и сложный прикол, вышедший из-под контроля.

LOLCODE - пастафарианизм (примечание переводчика: вера в Летающего Макаронного Монстра). Искусственное образование, появившееся в Интернете, которое никто не принимает всерьёз, несмотря на попытки его развивать и пропагандировать. Ещё одно примечание переводчика: отлично сочетается с культом Ктулху (кстати, а это какой язык?)

Visual Basic - сатанизм. Единственное отличие в том, что быть сатанистом, не продавая свою душу, всё-таки можно.

Спасибо jfs и другим участникам канала #aegisub за ценные предложения. Не забудьте, это шутка, а вовсе не попытка кого-то оскорбить. А если вы мусульманин, не убивайте меня, пожалуйста.

Originally posted on migmit.vox.com

16th December 2008

20:27: On Vox: Записки маньяка

Бэкграунд. Те, кто знает Ruby - пропустите.

В рубях есть такая фишка - собственно, основная - как "блоки". Каждому вызову метода можно дать как бы дополнительный параметр - блок кода. Эти блоки не являются настоящими значениями первого класса; однако, внутри метода их можно вызывать.

Метод:


class SomeClass
    ...
    def someMethod
        ... # что-то тут делаем
        yield # вызываем переданный блок
        ... # делаем что-то ещё
    end
    ...
end

Его вызов:

someInstance = SomeClass.new
...
someInstance.someMethod {... блок кода ...}

Вот, значит. Команда yield вызовет этот самый блок кода.

Замечание: из блока кода можно сделать настоящую лямбда-функцию, так что не говорите, что что-то не так.

Дальше - больше. Переданный блок может иметь параметры - скажем, так:


someInstance.someMethod {|i j| ... if i>j then...}

Соответственно, параметры передаются yield:

yield(1,2);

Ну, и что маньяк в моём лице попытался сделать в первую очередь? Ну разумеется, что-то такое:

...
def someMethod
    yield {бла-бла-бла}
end
...
someInstance.someMethod {yield}

С тем, чтобы метод вызвал переданный ему блок, в свою очередь передав этому блоку другой блок кода.

И что бы вы думали?

Не сработало.

Originally posted on migmit.vox.com

9th December 2008

21:36: On Vox: Присоединюсь

к другим приличным людям, вывесившим эту картинку.

Originally posted on migmit.vox.com

5th December 2008

23:18: On Vox: Начал понимать

что имел в виду Луговский, когда говорил о бедности русского мата в сравнении с английским.

Вот, например, из недавно прочитанного: охранник обнаруживает дверь, которую он, вроде как, должен был охранять, взломанной. Охранник: "Shit, shit, shit, fuck!"

Ну как это перевести? Чтобы сохранился не только общий смысл, но и скорость (произносится сие очень быстро).

Не представляю.

Originally posted on migmit.vox.com

29th November 2008

16:44: On Vox: Без особого повода

Уважаемые товарищи Progs.Kiev.ua!

В порядке ответа на ваше письмо, пришедшее с ящика noreply@что-то-там, сообщаю: вы пидарасы.

Всё остальное (как, например, то, что я НЕ собираюсь вкладывать от 1000 до 50000 долларов в развитие ваших проектов) с очевидностью следует из вышеизложенного.

Originally posted on migmit.vox.com

27th November 2008

23:49: On Vox: Розовая пантера
Meglio Stasera from http://migmit.vox.com/

Фран Джеффрис прекрасна.

Originally posted on migmit.vox.com

22nd November 2008

17:29: On Vox: Православное

Не могу понять, как относиться к развязавшейся последнее время истерии вокруг рублёвской иконы.

Для тех, кто в танке: РПЦ требует, чтобы им передали икону "Троица" авторства Андрея Рублёва. Музейные работники достаточно аргументированно указывают, что икона прожить вне музея сколько-нибудь долгое время не может.

Так вот. С одной стороны, было бы очень приятно, если бы конфликт разрешился в пользу музейщиков, потому как РПЦ очень нуждается в щелчке по носу, а лучше в ударе по голове (последнее, увы, недостижимо даже в воображении). С другой стороны, это ж не здание (где можно хотя бы картошку хранить), не земля - а икона. Предмет культа больных православием межушного нервного узла (с)piggy_toy. Если в результате всех этих пертурбаций она сдохнет - это уже будет солидный вклад в мировую культуру.

Поскольку события всегда развиваются по наихудшему сценарию, будет так. Икону передадут в церкву, она там быстро начнёт разрушаться, поэтому её (изобразив добрую волю) передадут обратно в музей, музейные работники сделают чудо и ПОЧТИ восстановят её (то есть, как предмет культа она сохранится), после чего их уволят за это самое "почти".

Originally posted on migmit.vox.com

15th November 2008

19:18: On Vox: А с континуэйшенами проще

Опять про ограниченные монады (см. предыдущий пост). Модуль "Suitable" остаётся тем же, что и был раньше, а вот основной тип данных меняется.

Что нам, фактически, нужно от значения типа "Restricted m a"? Нам нужно сбиндить его с последовательностью функций (первая - типа "a -> Restricted m b", вторая - "b -> Restricted m c" и т. д.), доведя до значения, для которого осмысленно запихивание в монаду "m", а затем сделать ему "unembed". Ну так мы сделаем это всё за один шаг, и этот самый шаг сделаем значением нашего нового типа.

Нам понадобятся existential types.


> {-# OPTIONS -fglasgow-exts #-}
> module RestrCont where
> import Control.Monad
> import qualified Data.Set as Set
> import Suitable
Основной тип данных:

> data RestrCont m a = RestrCont (forall b. Suitable m b => (a -> m b) -> m b)
В принципе, можно было бы написать "data RestrCont m a where RestrCont :: ...", используя GADT-ы вместо экзистеншиалсов. Преимущество такого подхода в том, что, если вместо класса "Suitable" использовать что-нибудь нормальное, типа класса "Ord", то полученная вещь заработает, например, в Hugs.

Объявляем наш тип монадой:


> instance Monad (RestrCont m) where
>     return x = RestrCont $ \h -> h x
>     RestrCont g >>= f = RestrCont $ \h -> g $ \x -> case f x of RestrCont g' -> g' h
В отличие от предыдущего примера, для того, чтобы сделать новый тип инстансом "MonadPlus" его не нужно менять; нужно лишь, чтобы само "m" поддерживало нужные операции.

> instance RestrPlus m => MonadPlus (RestrCont m) where
>     mzero = RestrCont $ const restrMZero
>     RestrCont g1 `mplus` RestrCont g2 = RestrCont $ \h -> g1 h `restrMPlus` g2 h
Операции "embed" и "unembed" тоже упрощаются. Первая из них использует только "restrBind"

> embed :: (RestrMonad m, Suitable m a) => m a -> RestrCont m a
> embed mx = RestrCont $ \h -> restrBind mx h
а вторая - только "restrReturn"

> unembed :: (RestrMonad m, Suitable m a) => RestrCont m a -> m a
> unembed (RestrCont g) = g restrReturn

Пример (ищите его в предыдущих постах) проходит без изменений.
Раньше, дабы соорудить некоторую алгебраическую операцию на "Restricted m" (из такой же алгебраической операции на m), нам нужно было изменить монаду "n" - так из "RestrPlus m" получалось "MonadPlus (Restricted m)". Сейчас это можно сделать, опять таки, проще:

> liftRestr :: Functor n => (forall a. Suitable m a => n (m a) -> m a) -> n (RestrCont m b) -> RestrCont m b
> liftRestr f nrmb = RestrCont $ \h -> f $ fmap (\(RestrCont g) -> g h) nrmb

Originally posted on migmit.vox.com

9th November 2008

22:53: On Vox: Ограниченное - часть 3

Всё изложенное ранее - очень хорошо, но относится к одному единственному классу - "Ord". Увы, мы не можем сделать класс параметром чего бы то ни было - тип можем, а вот класс - нет. Однако в Haskell-Café пробежало решение и этой проблемы.

Для этого решения требуются type families. Я вынесу в отдельный модуль как общий код, так и то, что нужно написать для "Set":


> {-# LANGUAGE TypeFamilies, MultiParamTypeClasses, FlexibleInstances #-}
> module Suitable where
Прагм многовато, но, за исключением первой, GHC подсказывает все остальные.

Мы используем "Set" для примера, так что импортируем соответствующий модуль:


> import qualified Data.Set as Set
Суть дела - в следующем классе; функция "constraints" не использует свой аргумент, ей нужен только его тип; все необходимые ограничения на тип "a" содержатся в типе "Constraints m a" - позднее мы увидим, как именно они задаются.

> class Suitable m a where
>     data Constraints m a :: *
>     constraints :: m a -> Constraints m a
Теперь можно ввести собственно "ограниченную монаду"

> class RestrMonad m where
>     restrReturn :: Suitable m a => a -> m a
>     restrBind :: (Suitable m a, Suitable m b) => m a -> (a -> m b) -> m b
и ограниченный вариант "MonadPlus":

> class RestrMonad m => RestrPlus m where
>     restrMZero :: Suitable m a => m a
>     restrMPlus :: Suitable m a => m a -> m a -> m a
Как и раньше, нам пригодится аналог функции "msum":

> restrMSum :: (RestrPlus m, Suitable m a) => [m a] -> m a
> restrMSum = foldr restrMPlus restrMZero
Теперь посмотрим, как это всё работает для "Set". Для начала - класс "Suitable".

> instance Ord a => Suitable Set.Set a where
>     data Constraints Set.Set a = Ord a => SetConstraints
>     constraints _ = SetConstraints
Как видим, и класс "Suitable" и тип "Constraints" определены лишь тогда, когда "a" относится к классу "Ord". Это и есть то ограничение, которое нам нужно. Функция "constraints" позволяет нам проверять это ограничение, как, например, в определении инстанса "RestrMonad"

> instance RestrMonad Set.Set where
>     restrReturn = Set.singleton
>     restrBind sx f = let result = case constraints result of SetConstraints -> Set.unions $ map f $ Set.toList sx in result
или инстанса "RestrPlus".

> instance RestrPlus Set.Set where
>     restrMZero = Set.empty
>     restrMPlus sx1 sx2 = case constraints sx1 of SetConstraints -> Set.union sx1 sx2
Теперь основной модуль выглядит ровно так же, как и раньше, только функции, начинающиеся на "ord" заменяются функциями, начинающимися на "restr".
> {-# LANGUAGE GADTs #-}
> module Restricted where
> import Control.Monad
> import Control.Monad.Identity
> import Control.Monad.List
> import Control.Monad.Trans
> import qualified Data.Set as Set
> import Suitable
>
> data RestrTData m n a where
>     RestrRet :: a -> RestrTData m n a
>     RestrBind :: Suitable m a => m a -> (a -> RestrT m n b) -> RestrTData m n b
> newtype RestrT m n a = RestrT {fromRestrT :: n (RestrTData m n a)}
> instance Monad n => Monad (RestrT m n) where
>     return = RestrT . return . RestrRet
>     RestrT nrmnx >>= f = RestrT $ nrmnx >>= handleRmnx
>         where handleRmnx (RestrRet x) = fromRestrT $ f x
>               handleRmnx (RestrBind mu g) = return $ RestrBind mu $ \x -> g x >>= f
> instance MonadTrans (RestrT m) where lift nx = RestrT $ liftM RestrRet nx
> embed mx = RestrT $ return $ RestrBind mx return
> unembed f = unembed'
>     where unembed' (RestrT nrmnx) = f $ liftM handleRmnx nrmnx
>           handleRmnx (RestrRet x) = restrReturn x
>           handleRmnx (RestrBind mu g) = restrBind mu $ unembed' . g
> instance MonadPlus n => MonadPlus (RestrT m n) where
>     mzero = RestrT mzero
>     RestrT nrmnx1 `mplus` RestrT nrmnx2 = RestrT $ nrmnx1 `mplus` nrmnx2
> type RestrM m a = RestrT m Identity a
> unembedId :: (RestrMonad m, Suitable m a) => RestrM m a -> m a
> unembedId = unembed runIdentity
> unembedPlus :: (RestrPlus m, Suitable m a) => RestrT m [] a -> m a
> unembedPlus = unembed restrMSum
Пример тоже выглядит в точности как раньше:

> test = unembedPlus $ do x <- embed $ Set.fromList [6, 2, 3]
>                         (do y <- return x
>                             z <- embed $ Set.fromList [1..2]
>                             guard $ y < 5
>                             return $ y + z)
>                         `mplus` return 10
Вот и всё.

Originally posted on migmit.vox.com

21:43: On Vox: Ограниченное - часть 2

В предыдущем решении один и тот же, по существу, код повторялся дважды. Это не есть хорошо. Вполне можно написать то же самое ОДИН раз, используя "Identity" в первом случае и "[]" во втором. Ну и, интуиция подсказывает, что нужно использовать тот факт, что и то, и другое является монадой.

Нам понадобится чуть больший список импортов:


> {-# LANGUAGE GADTs #-}
> module Rest2 where
> import Control.Monad
> import Control.Monad.Identity
> import Control.Monad.List
> import Control.Monad.Trans
> import qualified Data.Set as Set
> import OrdMonadSet
Тип данных мы определим так же, как и "OrdP" ранее, только вместо "[]" у нас будет произвольное "n".

> data OrdTData m n a where
>     OrdReturn :: a -> OrdTData m n a
>     OrdBind :: Ord a => m a -> (a -> OrdT m n b) -> OrdTData m n b
> newtype OrdT m n a = OrdT {fromOrdT :: n (OrdTData m n a)}
Снова так же определяется инстанс "Monad".

> instance Monad n => Monad (OrdT m n) where
>     return = OrdT . return . OrdReturn
>     OrdT nomnx >>= f = OrdT $ nomnx >>= handleOmnx
>       where handleOmnx (OrdReturn x) = fromOrdT $ f x
>             handleOmnx (OrdBind mu g) = return $ OrdBind mu $ \x -> g x >>= f
Теперь, "OrdT m n" является монадой, если "n" является монадой. То есть, "OrdT m" - это такой преобразователь монад. Трансформер, иначе говоря. Для полноты картины можно объявить и инстанс "MonadTrans".

> instance MonadTrans (OrdT m) where lift nx = OrdT $ liftM OrdReturn nx
Я не знаю, зачем это может понадобиться, но пусть уж будет, хотя бы для красоты.

Функция "embed" выглядит как и раньше, а вот "unembed" - не совсем. В предыдущем посте "unembed" и "unembedPlus" различались - в unembedPlus участвовала функция "ordMSum". Поскольку её аналог для произвольного "n" заранее неизвестен, придётся передавать её в "unembed" как параметр.


> embed mx = OrdT $ return $ OrdBind mx return
> unembed f = unembed'
>     where unembed' (OrdT nomnx) = f $ liftM handleOmnx nomnx
>           handleOmnx (OrdReturn x) = ordReturn x
>           handleOmnx (OrdBind mu g) = ordBind mu $ unembed' . g
Ну, теперь несложно воспроизвести старый "OrdM":

> type OrdM m a = OrdT m Identity a
> unembedId :: (OrdMonad m, Ord a) => OrdM m a -> m a
> unembedId = unembed $ runIdentity
Далее, если "n" - не просто монада, но инстанс "MonadPlus" (как, скажем, "[]"), то "OrdT m n" - тоже инстанс "MonadPlus"; кроме того, если "m" - инстанс "OrdMonadPlus", то можно воспроизвести старое "unembedPlus":

> instance MonadPlus n => MonadPlus (OrdT m n) where
>     mzero = OrdT mzero
>     OrdT nomnx1 `mplus` OrdT nomnx2 = OrdT $ nomnx1 `mplus` nomnx2
> unembedPlus :: (OrdMonadPlus m, Ord a) => OrdT m [] a -> m a
> unembedPlus = unembed $ foldr ordMPlus ordMZero
Наш пример теперь выглядит так:

> test = unembedPlus $ do x <- embed $ Set.fromList [6, 2, 3]
>                         (do y <- return x
>                             z <- embed $ Set.fromList [1..2]
>                             guard $ y < 5
>                             return $ y + z)
>                         `mplus` return 10

Originally posted on migmit.vox.com

21:08: On Vox: Ограниченное - часть 1

Некоторое время тому назад уважаемый [info]hsenag описал способ превратить так называемую "ограниченную монаду" (restricted monad) в настоящую. Я в комментах там описал способ попроще и поэлегантнее; позднее я его расширил и теперь представляю интересующимся.

Что такое, собственно, ограниченная монада? Это то же самое, что и обычная монада, но имеющая смысл только тогда, когда тип, который она содержит, относится к некоторому классу. Характерный пример - множество, "Set". Мы не можем образовать "Set a", если "a" не относится к классу "Ord". В то же время, "Set" не так уж сильно отличается от "[]", и можно ожидать, что стандартный "do"-синтаксис окажется полезным и здесь. Однако, из-за указанного ограничения, превратить "Set" в монаду нельзя.

То, что я сказал, можно превратить в описание класса "OrdMonad" и его инстанса для "Set":


> module OrdMonadSet where
> import qualified Data.Set as Set

> class OrdMonad m where
>     ordReturn :: Ord a => a -> m a
>     ordBind :: (Ord a, Ord b) => m a -> (a -> m b) -> m b

> instance OrdMonad Set.Set where
>     ordReturn = Set.singleton
>     s `ordBind` f = Set.fold (\v ret -> f v `Set.union` ret) Set.empty s
Далее, в соответствии с тем, что сделано для монады "[]", можно также объявить аналог "MonadPlus":

> class OrdMonad m => OrdMonadPlus m where
>     ordMZero :: Ord a => m a
>     ordMPlus :: Ord a => m a -> m a -> m a

> instance OrdMonadPlus Set.Set where
>     ordMZero = Set.empty
>     ordMPlus = Set.union
В дальнейшем нам пригодится аналог функции "msum":

> ordMSum :: (OrdMonadPlus m, Ord a) => [m a] -> m a
> ordMSum = foldr ordMPlus ordMZero
Как же заставить "do"-синтаксис работать с такими "ограниченными монадами"? Ну, один из вариантов - использовать Template Haskell и руками преобразовать do-нотацию в выражение с "ordReturn" и "ordBind". Однако, использовать TH - значит признать своё поражение; мы поищем другой способ.

Один из способов сделать это внутри хаскеля описан в посте, на который выше стоит ссылка. Желающие могут посмотреть, как это сделано; я же изложу собственный вариант.

Для начала нам потребуются GADT-ы (без них, увы, никуда, разве что existential types вспомнить, а это то же самое).


> {-# LANGUAGE GADTs #-}
> module Rest1 where
> import Control.Monad
> import qualified Data.Set as Set
> import OrdMonadSet
Последний импорт - это импорт того модуля, который приведён выше; он содержит, в частности, класс "OrdMonad".

Тип данных, который мы будем использовать, содержит, собственно, два конструктора - аналоги >>= и return. Однако, поскольку цепочка >>= будет всегда начинаться с чего-то, относящегося к классу "Ord", мы можем ограничить один из типов:


> data OrdM m a where
>     ReturnOrdM :: a -> OrdM m a
>     BindOrdM :: Ord a => m a -> (a -> OrdM m b) -> OrdM m b
Теперь несложно объявить этот тип монадой. При этом мы будем использовать монадические законы - например, то, что "return x >>= f = f x". Именно эти законы предопределяют определение ">>=" для нашего типа "OrdM".

> instance Monad (OrdM m) where
>     return = ReturnOrdM
>     ReturnOrdM x >>= f = f x
>     BindOrdM mx g >>= f = BindOrdM mx $ \x -> g x >>= f
Далее, нужно найти способ перегнать нашу "ограниченную монаду" в настоящую:

> embed :: Ord a => m a -> OrdM m a
> embed mx = BindOrdM mx return
и обратно:

> unembed :: (OrdMonad m, Ord a) => OrdM m a -> m a
> unembed (ReturnOrdM x) = ordReturn x
> unembed (BindOrdM mx g) = mx `ordBind` (unembed . g)
Уже неплохо. Однако, нам нужно работать ещё и с "OrdMonadPlus". Увы, даже если есть инстанс "OrdMonadPlus" для "m", превратить "OrdM m" в инстанс "MonadPlus" не удаётся.

Решение по ссылке выше - добавить в "OrdM" парочку конструкторов, скажем, "OrdMZero" и "OrdMPlus". Мне это решение не нравится, так как не учитывает законы, связанные с "mzero" и "mplus" - например, ассоциативность. Вместо этого я буду просто хранить список, элементы которого - по сути те же, что и раньше. "mzero" превратится в пустой список, а "mplus" - в конкатенацию.


> data OrdPlusData m a where
>     ReturnOrdPlus :: a -> OrdPlusData m a
>     BindOrdPlus :: Ord a => m a -> (a -> OrdP m b) -> OrdPlusData m b

> newtype OrdP m a = OrdP {fromOrdP :: [OrdPlusData m a]}
Для "OrdP" я объявлю - по сути, точно так же - инстанс класса "Monad":

> instance Monad (OrdP m) where
>     return x = OrdP [ReturnOrdPlus x]
>     OrdP omxs >>= f = OrdP $ omxs >>= handleOmx
>         where handleOmx (ReturnOrdPlus x) = fromOrdP $ f x
>               handleOmx (BindOrdPlus mx g) = [BindOrdPlus mx $ \x -> g x >>= f]
Правда, разбор случаев пришлось упаковать во вспомогательную функцию "handleOmx"; заметьте, что в выражении ">>= handleOmx" оператор ">>=" - это ">>=" для монады-списка, то есть, по сути, "concatMap".

Теперь можно определить и инстанс "MonadPlus":


> instance MonadPlus (OrdP m) where
>     mzero = OrdP []
>     OrdP omxs1 `mplus` OrdP omxs2 = OrdP $ omxs1 ++ omxs2
Наконец, нам понадобятся варианты функций "embed" и "unembed" для "OrdP":

> embedPlus :: Ord a => m a -> OrdP m a
> embedPlus mx = OrdP [BindOrdPlus mx return]

> unembedPlus :: (OrdMonadPlus m, Ord a) => OrdP m a -> m a
> unembedPlus (OrdP omxs) = ordMSum $ map handleOmx omxs
>     where handleOmx (ReturnOrdPlus x) = ordReturn x
>           handleOmx (BindOrdPlus mx g) = mx `ordBind` (unembedPlus . g)
Это те же определения, что и для "OrdM", только опять-таки разбор случаев запихнут в ">>= handleOmx"

Теперь можно писать такие, например, вещи:


> test = unembedPlus $ do x <- embedPlus $ Set.fromList [6, 2, 3]
>                         (do y <- return x
>                             z <- embedPlus $ Set.fromList [1..2]
>                             guard $ y < 5
>                             return $ y + z)
>                         `mplus` return 10

Запускаем, получаем:

*Rest1> test
fromList [3,4,5,10]

Originally posted on migmit.vox.com

30th October 2008

00:09: On Vox: Счёт за телефон

и как на него следует реагировать:

 
Девушка здесь - Джейн Фонда.

Originally posted on migmit.vox.com

10th October 2008

01:20: On Vox: Шекспир

Никогда не был большим поклонником его сонетов. Однако, когда один из них читает гениальный актёр - даже меня пронимает.



Originally posted on migmit.vox.com

5th October 2008

18:34: On Vox: Гум-манитарии

avva призвал желающих присоединиться к нему в изучении латыни. OK, я решил попробовать. Прочитал инструкцию. Согласно прочитанному, подписался на их список рассылки (ДО ФИГА ненужного хлама) и написал ведущему курс.

В ответ - гробовое молчание. Ждал некоторое время, сегодня утром решил, что хватит. Отписался от рассылки. После этого пришло письмо от ведущего. С первым заданием. И указанием, к какому сроку его надо выполнить. Ну, вы поняли. Вчера.

В этом письме были указаны несколько адресов страниц, посвящённых его курсу. Часть не работает, часть работает, но относится к курсу за прошлый год.

Sorry, guys, but I give up.

Originally posted on migmit.vox.com

13:55: On Vox: МИГМиТ

Международный Институт Гостиничного Менеджмента и Туризма.
Блядь.

Originally posted on migmit.vox.com

4th October 2008

22:12: On Vox: Ох, какой срач развели!

На тему "ФП versus ООП".

Кому интересно - заходите на огонёк:

http://ady-1981.livejournal.com/16012.html

Originally posted on migmit.vox.com

25th September 2008

20:57: On Vox: JavaScript

Таки нарвался.
Internal error: too much recursion.
Блин.

Originally posted on migmit.vox.com

24th September 2008

19:14: On Vox: Вечерние разговоры

Собеседник - Считаем у этого сигнала энергетический спектр...
MigMit - А кто такой энергетический спектр?
С - Это амплитудный спектр.
M - Объяснил, однако.


M - Предположим, что в последовательности бит комбинация "01" встечается в четыре раза чаще, чем комбинация "10"...

Только через минуту дошло, что глупость сморозил.

Originally posted on migmit.vox.com

22nd August 2008

15:16: On Vox: MPlayer

Может, это я такой тормоз, но раньше я такого прикола не замечал. Качал видео в wmv через mplayer -dumpstream. Оно докачалось и сказало:

Everything done. Thank you for downloading a media file containing proprietary and patented technology.

Originally posted on migmit.vox.com

9th August 2008

15:58: On Vox: HJKL

Во всех учебниках по ViM-у эти клавиши указаны если не в самом начале, то близко. И ни разу я не смог их запомнить - потому как стрелки всегда под рукой.
Стоил мне запустить ViM на iPod-е - и сабжевые клавиши запомнились как миленькие. Потому что там стрелок нет.
Вот так.

Originally posted on migmit.vox.com

02:30: On Vox: Наваждение какое-то

Похоже, компьютеры меня боятся.
Летал тут отдыхать в Сингапур (кстати, прикупил iPod Touch дешевле в полтора раза, чем в России, и доволен сим девайсом как слон). Ключевое слово - "летал". Потому что в новых Боингах, стоящих на вооружении Singapore Airlines, перед каждым пассажиром расположен дисплей, на котором можно посмотреть кино, поиграть в "Морской бой" или просто посмотреть информацию о полёте.
Так вот, у меня эта штука (именно у меня, а не у соседей) вдруг зависла. Повисев немного, она перезагрузилась. На этом месте мне стало немного не по себе - вроде, ничего криминального не делал, но как-то неуютно.
Перезагрузившись, она попала в MS-DOS:

А затем моя челюсть упала на пол:
Для счастливчиков поясняю: сие есть Windows 3.1 (или родственница).
После этого, к моему облегчению, снова загрузилась обычная оболочка.

Originally posted on migmit.vox.com

26th July 2008

23:55: On Vox: Прогулки с телефоном

Телефон, как известно, служит для того, чтобы фотографировать. Я этим не слишком увлечён, но оно таки копится. Ну и вот.

Для затравки, стандартный сюжет: банковский терминал вылетает в интерфейс виндов. Здесь, для разнообразия, он вылетел дальше:

Ближе я подойти не успевал, щёлкал через стекло.

Развивая тему: видимо, кого-то сильно достали спамеры:
Все знают, что такое "успешный менеджер", но никто его не видел. А вот:
С какого количества песчинок начинается куча - неизвестно до сих пор. В основном из-за недостатка терпения. Но вот с какого количества веб-страниц становится МНОГО - известно. Правда, единица измерения странная: Нет, я не боюсь сделать рекламу этому придурку.

Здесь же уместно помянуть информацию о стоимости сайта (и пусть А. Лебедев удавится от зависти):
Особенно эстетично смотрится решёточка.

Кстати о решётках: вот такая у нас завелась штука:

Следующее напоминает известный анекдот про то, как полезно изучать английский в СПбГУ: Честно говоря, я затруднюсь это перевести обратно на русский.

Ну и, чтобы (временно) закрыть тему - три, а точнее, шесть, объявлений, повергающих меня в ступор - кому верить?
DSC00151DSC00080DSC00079

Вам нужно алиби? Вам сюда: Других вывесок там не было, и я так и не узнал, что же это на самом деле такое.

Наши начинающие - всем начинающим начинающие: Интересно, что фотографировал я это в Публичке (или как там её сейчас).

В городе появились навозные жуки-гиганты:
Эти, что ли?

Originally posted on migmit.vox.com

22nd June 2008

03:40: On Vox: Мастера

и Маргариты.
Посмотрел фильм Кары, точнее, его заготовку - четыре серии по пятьдесят минут. Откуда взял - уже не помню. Сразу говорю: качество изображения - отвратное. Кроме того, видно, что фильм недомонтировали. Плюс к тому, местами картинка дёргается, как будто из испортившейся плёнки вырезали по полсекунды в разных местах. За все эти художества булгаковскому потомку, не давшему доделать фильм - анафема.
Надеюсь, всем понятно, что без сравнения не обойтись?
Басилашвили - на две головы выше Гафта, и вообще как актёр, и в роли Воланда в частности. Воланд у Бортко - владыка зла, повелитель теней; у Кары - профессор чёрной магии, за каким-то хреном выдающий себя за сатану.
Мастер - тут тоже однозначное преимущество у озвученного Безруковым Галибина. Раков, по-моему, вообще играть не очень умеет, а уж в роли Мастера - даже и не смотрится. Галибин же - совершенно никакой режиссёр, но отличный актёр (ох, каков был Пашка Америка!)
Маргарита - Ковальчук опять же гораздо лучше Вертинской, которую так и хочется назвать "стареющей". Плюс, я конечно понимаю, что актрису не это определяет - но Вертинская очевидно боится раздеваться (и правильно, кстати).
Бездомный - ИМХО, и Гармаш, и Галкин сыграли хорошо. Совершенно разные Бездомные, но оба убедительны.
Бегемот в кошачьей ипостаси - наоборот, одинаково плохо что там, что там. Видимо, если решил снимать "М&М", то надо первым делом думать над тем, как сделать кота. А вот Бегемот в человеческом облике у Бортко гораздо выразительнее.
Обратно - в качестве Пилата Ульянов убедителен, Лавров - нет. Ну не верю я в то, что передо мной - жёсткий правитель из бывших военных. Ульянову верю, Лаврову - нет.
Положительно не знаю, как делить Филиппенко. В обеих ролях - Коровьев у Кары и Азазелло у Бортко - он существенно убедительнее, чем аналогичный персонаж в другой версии. Пожалуй, всё-таки предпочту Коровьева. Кара сумел уловить главное, на мой взгляд, в образе Коровьева - шутовство. И в сцене с Босым, и в сцене с Поплавским, Абдулов убийственно серьёзен, и это губит обе сцены. Филиппенко же кривляется, клоунничает - как это делает Коровьев в романе. И это прекрасно работает.
Забавно, что в сцене у грибоедовского забора происходит почти одно и то же. Абдулов чешет у Бегемота за ухом - Филиппенко чешет Бегемоту живот. У Булгакова этого, сколько я помню, нет.
Ну и Гелла - Таню Ю я не воспринял вообще; она не играет, она брезгливо соглашается участвовать. Гелла у Кары - нормальная ведьмочка.
А теперь главное. У Бортко, в общей сложности, актёрский ансамбль лучше - но в нём актёры, по сути, предоставлены самим себе. Басилашвили тянет свою роль один - Гафту помогает режиссёр. Сценарий Бортко представляет собой с кровью и мясом выдранные куски романа, приляпанные друг к другу как попало без всякой попытки по косвенной речи соорудить прямую. В сценарии же Кары люди разговаривают нормально, чувствуется, что над текстом работали, и работали вдумчиво. Есть интересные находки, отсутствующие в романе, но вполне подходящие по духу - скажем, Аннушка завершает булгаковское "мерси,.. мерси..." вполне логичным окончанием "...сволочь". Или допрос Босого - "Откуда доллары в вентиляции? - Сами образовались!" Или Сталин и Гитлер на балу, с обменом репликами между Маргаритой и Коровьевым - "Они же ещё живы? - А это специально приглашённые".
Отдельная статья - спецэффекты. Бортко, помнится, в каком-то интервью распинался о том, сколько, дескать, денег угрохано на компьютерную графику. Так почему, блин, эффекты у Кары смотрятся не хуже? Почему полёт над Москвой у Бортко полностью завален - а у Кары, если отвлечься от главной героини, смотрится отлично? Почему Каре достаточно показать лес в негативе, чтобы создать ощущение сказочности - а у Бортко этого ощущения не возникает в принципе? Справедливости ради - картинку собственно бала завалили оба.
Вот так. Взять бы таких актёров - и дать их такому режиссёру/сценаристу, а булгаковских наследников утопить в дерьме.

Originally posted on migmit.vox.com

15th June 2008

01:55: On Vox: Не забыть:

Чтобы скачать что-то с mihd.net нужно дождаться, пока всё устаканится, и из файербага выполнить функцию ticket.detected(). Тогда появится надпись "Download", котороая и есть ссылка на файл. Иначе на заработает.
Уроды.

Originally posted on migmit.vox.com

Powered by LiveJournal.com

Advertisement