Картинка из игры, которую мы сейчас делаем. Без комментариев.
Originally posted on migmit.vox.com
MiguelRecent Entries | ||
|
You are viewing the most recent 50 entries.
18th November 2009
: On Vox: Текущая работа
Картинка из игры, которую мы сейчас делаем. Без комментариев. Originally posted on migmit.vox.com 16th November 2009
: On Vox: Игрушечный веб 2.0
Так как грёбаный ЖЖ не пропускает длинные посты, а бить на десять частей я не собираюсь, выложено только на Vox: клик сюды Originally posted on migmit.vox.com 14th October 2009
: On Vox: Мысли в кучу
Смотрю новый сериал FlashForward. Сюжет пересказывать не буду, желающие узнают всё необходимое, например, вот здесь. Просто соберу в кучу некоторые вопросы и теории, а потом посмотрим - что из этого оправдается, и на что будет дан ответ.
Originally posted on migmit.vox.com 7th October 2009
: 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_he Вот так вот. Originally posted on migmit.vox.com 26th September 2009
: On Vox: Code Jam
Итак, оно кончилось. 759 место, дальше не прохожу. Решил первую задачу и первую часть третьей - оно таки обломалось на large set. Вывод: с некоторыми базовыми алгоритмами у меня, всё-таки, плохо. Originally posted on migmit.vox.com 1st September 2009
: On Vox: Почувстовал себя персонажем анекдота
когда набрал в терминале "which watch". Originally posted on migmit.vox.com 17th August 2009
: On Vox: Первый аплоад на Hackage
compose-trans-0.0 Сделан по мотивам вот этого поста. Очень сильно отрефакторено и упрощено. Originally posted on migmit.vox.com 10th August 2009
: On Vox: Какой австралопитек
делал функцию "Родительский контроль" в винде? Неужели он не мог посмотреть хотя бы на макось? Как вообще в его микроскопический мозг пришла мысль, что родитель должен задавать не общее время, которое чадо проводит за компом, а конкретные часы? Не "два часа в день", а "с 17:30 до 19:30"? Или эта хуйня разрабатывалась изначально для использования в пенитенциарных учреждениях? Почему, интересно, в макоси, да и в любом другом юниксе, никого не ебёт, какие программы юзер установит для себя лично, главное, чтобы не пытался лезть в чужие данные - а в этой куче дерьма под названием Vista по умолчанию установка софта запрещена всем не-админам? Это надо понимать как признание, что ихний выкидыш представляет собой глюк на глюке, который упадёт от первого залетевшего дятла? Какого хрена? Раньше я думал, что под админом работают только идиоты. Похоже, что в винде другого варианта вообще нет. Ощущение такое, что виста - это не ОС, а демка. А винда Home Basic, которая шла вместе с компом - демка от демки. Повбывав бы. Уроды. Все. Originally posted on migmit.vox.com 30th June 2009
: On Vox: Просто забавно
Вчера всем, конечно, было не до того. Но сегодня должны были уже отойти, так что: Originally posted on migmit.vox.com 16th June 2009
: On Vox: Ну и денёк
Началось, как обычно, с мелочи. Дизайнеры сделали новую модель игрока, заменив устрашающий солдафонский костюм на футболку и джинсы, не менее устрашающие. В какой-то момент игрок посмотрел в небо, слегка отклонившись при этом назад. С другой позиции сразу стало видно, как автомат, висевший у игрока за спиной, прошёл у него между ногами и нагло торчит дулом аккурат из ширинки. Особо впечатлительные крестились и украдкой прикасались к томику Фрейда. Originally posted on migmit.vox.com 19th May 2009
: On Vox: TWIMC
Если кому интересно, то начистить мне чайник можно здесь: http://migmit.mybrute.com Originally posted on migmit.vox.com 15th May 2009
: 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, нам нужно использовать, что аргумент засунут именно в монаду, а не во что-то ещё. Действительно нужно, это не фантазия какая-то.Попробуем пофиксить, изменив сигнатуру 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) пока лишён важной утилиты, а именно, компилятора. Нет, интерпретатора тоже нет. Так что, этот способ тоже не сработает.Попробуем иначе. Что нам нужно, так это добавить в класс Попробуем это сделать. Что вообще означает, что некоторый тип > type (m :-> n) = forall x. m x -> n xВот они - морфизмы нашей новой категории. Далее, опять же, теория категорий учит, что новую монаду нужно определять так: объекту 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 и (>>=) для полного счастья. Сейчас мы их определим.Начнём с > 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.Теперь мы видим, что функция *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.Как же это поможет нам решить нашу проблему? А вот как: по сути дела, указать для некоторого типа отображение > 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Обычно мы пошли бы по маршруту Увы, если первый и последний шаги особых проблем не представляют, то второй шаг, увы, невозможен, так как Делаем: 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).Если бы 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, признаюсь, написал не я, а компилятор. Ну, пусть будет.Аналогично пишется инстанс для > 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). В остальном же, мы просто в правой части повторяем левую.Теперь можно писать, например, Если кто-то вдруг захочет написать собственный трансформер 1) instance Monad m => Monad (MyCoolTransformer m)Если этого не сделать, то непонятно, почему вообще речь идёт о трансформерах монад. 2) lift :: m x -> MyCoolTransformer m xЭто - то, для чего трансформеры монад действительно нужны. 3) Заклинание Маленькое замечание: здесь мы почти не пользовались тем, что речь идёт именно о монадах. Точно то же самое можно написать про трансформеры, например, стрелок. Понадобиться только а) изменить понятие морфизма, так как стрелки имеют другой kind, б) заменить два инстанса на полностью аналогичные, один для нашей "монады" (которая, если мы заменим монады на стрелки,.. останется монадой), и один для оператора применения трансформера к Originally posted on migmit.vox.com 25th April 2009
: On Vox: Когда-то, когда я был гораздо моложе...
я пытался освоить Лисп. И там была одна вещь, которую моё подсознательное всякий раз отвергало. Я в принципе не мог понять, как это - результатом конструкций типа progn является результат последнего выражения. А куда же деваются результаты остальных??? Нет, разумом я понимаю: они производят некий сайд-эффект. Проблема в том, что то, что должно возвращать значение, и то, что по смыслу никакого значения возвращать не должно, а нужно только для сайд-эффекта, глазом не различается никак. Поэтому принять эту концепцию сердцем я не мог. Мне всё время казалось, что если результат этой штуковины не нужен, то её можно будет просто выкинуть, она нафиг не нужна. Даже в Паскале сразу очевидно - здесь у нас ":=" и интересует нас возвращаемое значение; а здесь у нас никакого ":=" нет, и интересует нас сайд-эффект. И поэтому основной частью do-синтаксиса в Хаскеле я считаю синтаксическую разницу между действием и связыванием переменной: do action или do var <- expression Originally posted on migmit.vox.com 22nd April 2009
: On Vox: Игрушечный веб - 3
Я таки сделал этот чёртов ArrowLoop! Не буду бить на несколько модулей - на винчестере у меня сейчас всё уже сильно не так, сделано довольно много изменений, так что я просто напишу, как делать ArrowLoop - используя при этом три модуля из первого постинга на эту тему. Для начала - шапка:
Здесь нет ничего особо интересного. Единственное что - я импортирую Control.Monad.Fix, потому что в одном месте мне будет удобно явно написать функцию fix. Тип Signal из предыдущего постинга претерпел некоторые изменения - в частности, он перестал быть монадой и стал функтором:
Кроме того, он является АДДИТИВНЫМ функтором - и я слегка офигел, обнаружив, что в стандартной библиотеке такого класса нет:
Старый тип Signal восстанавливается из нового, а его instance Monad - из instance Additive нового:
Теперь старый тип Signal становится SignalMonad (Signal). Получился симпатичный рефакторинг. Однако, нам не нужен старый тип Signal. Нам нужен его вариант, имеющий не только выход, но и вход, причём (!) часть его входа может зависеть от выхода. Именно наличие такой зависимости делает возможным создание instance ArrowLoop. Делаем:
От Kleisli(SignalMonad Signal) это отличается только тем, что вместо input в одном месте стоит (output -> input). Вот она и зависимость. Далее - довольно стандартные инстансы. Основная идея композиции таких стрелок - если мы знаем, как возвращать сигнал из конца в начало, а нам нужно вернуть его из СЕРЕДИНЫ в начало, то мы сначала протаскиваем его в конец, а потом возвращаем в начало известным способом. Аналогично, если нужно вернуть сигнал из конца в середину - мы возвращаем его в начало, а затем протаскиваем в середину.
Функция first требует некоторого допинывания ногами, но, как только нам удаётся удовлетворить тайпчекер - всё работает.
Теперь обещанный ArrowLoop. Мы специально постарались сделать всё так, чтобы можно было его написать - ничего удивительного, что он таки написался, причём легко.
Наконец, самое забавное. ArrowChoice. Фишка в том, что ArrowChoice даёт нам возможность, в зависимости от приходящих сигналов, рендерить разные части виджета. При этом мы не хотим, чтобы сигнал, пройдя через виджет и вернувшись назад по какому-то циклу, поменял выбор той части, которая должна рендериться. Смена отображаемого куска должна происходить только между загрузками страницы, но не во время. Гарантировать это статически мы не можем никак. Поэтому я сознательно допускаю возможность, что в этом месте вычисление упадёт с ошибкой. Оно не должно падать - и не будет, если страница написана нормально.
Собираем всё это вместе, не забыв, как обычно, добавить состояние:
На вход всей страницы всегда подаётся (), а локальное состояние зачитывается из пришедшего от пользователя URL. Выход страницы игнорируется - поэтому, обратной связи, фактически, не будет - точнее, вместо функции она будет константой:
Теперь нужны label, link и state - почти такие же, как в прошлом постинге. Для начала - label. Выход label - всегда (), поэтому обратная связь не может быть ничем, кроме константы; нас интересует, следовательно, её единственное значение:
Вход link - всегда (), поэтому обратная связь может быть только const (). Поэтому, мы её вообще проигнорируем.
Ну и, наконец, state. State не отображается никак, а потому не интересуется обратной связью.
Готово. Попробуем, чтобы убедиться, что старые примеры продолжают работать:
Загружаем в GHCi:
Теперь убедимся, что новые фокусы тоже работают:
В этом примере всё почти также, как и в test1 - только ссылка, изменяющая счётчик, расположена ПОСЛЕ самого счётчика. Это было невозможно со старой реализацией, зато с новой:
Работает, однако. Чувствую, пора из игрушечного фреймворка делать полноразмерный. Последнее замечание: виджет-хамелеон, который упоминался в прошлый раз, по-прежнему не делается. И я не уверен, что его удастся сделать более-менее разумным образом. Originally posted on migmit.vox.com 14th April 2009
: On Vox: Офигительно
Довольно банальная завязка - американка, вышедшая замуж за англичанина,
приезжает в его дом и знакомится с его семьёй, явно её не одобряющей -
превратилась в классно сыгранный, классно поставленный фильм с классным
саундтреком. Рекомендую - Easy Virtue, или "Лёгкое поведение". Кстати,
в переводе, вроде бы, идёт в наших кинотеатрах прямо сейчас. Originally posted on migmit.vox.com 7th April 2009
: 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 +В первый раз мы подаём на вход 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 +Работает. Третий пример: размещаем на странице ДВА виджета из первого примера. По идее, они должны работать независимо: > test3 = > proc () -> > do test2 -< () > test2 -< ()И тестируем: *HTML> putStr $ renderPage test3 $ Nothing +И опять работает. Четвёртый пример: своего рода "визард" с двумя страницами, с кнопкой для переключения. На каждой странице мы разместим виджет из второго примера: > 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Чего здесь не хватает? Во-первых, каждый виджет может влиять лишь на те виджеты, которые идут после него. Для влияния "назад" нам понадобился бы 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
: On Vox: Игрушечный веб - 1
Как-то странно получается. Я активно не люблю стрелки (имеются в виду, естественно, хаскельные Arrows), и, тем не менее, постоянно их сочиняю, как правило, применительно к вебу. На этот раз речь пойдёт о задачке, которую несколько невнятно сформулировал Речь о том, чтобы сымитировать десктопное приложение в вебе, не прибегая к помощи джаваскрипта и не храня ничего на сервере. Для простоты мы ограничимся выводом текста и кнопками - в роли которых у нас будут выступать ссылки. Задумка в том, чтобы клик по ссылке работал как нажатие кнопки, меняя состояние виджетов на странице (т.е., в основном, меняя отображаемые надписи). При этом, состояние виджетов, не имеющих отношения к этой кнопке, должно, естественно, сохраняться. Отсюда вытекает, что в каждой ссылке должно быть прописано состояние всех виджетов вообще, которые есть на странице - и в то же время мы хотим писать виджеты, содержащие ссылки, не зная заранее, что на странице будет ещё. Итак, в бой. Задача прикручивания всего этого к какому-нибудь веб-серверу (например, 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
: On Vox: О флагах
Продолжается процесс выбора логотипа для Хаскеля. У меня потихоньку закрадываются сомнения в том, что этот процесс является сходящимся. А вот я сегодня увидел где-то флаг Чехии: Originally posted on migmit.vox.com 17th March 2009
: On Vox: Взаимность
Когда в Haskell-cafe обсуждают теоркат - это нормально. Теперь в рассылке по теоркату начали обсуждать Haskell. И весьма активно, приводя примеры кода. Originally posted on migmit.vox.com 16th February 2009
: On Vox: Интересно
в хохмагазинах можно купить компас со стрелкой, намагниченной поперёк? Originally posted on migmit.vox.com 9th February 2009
: On Vox: Просамоцитируюсь
Из комментов к посту про платную/бесплатную медицину на ЛОРе: > Сервис - ничуть не хуже коммерческого. Ты пробовал когда-нибудь попадать в больницу? Обычную, бюджетную? Меня бог пока миловал. Зато некоторых родственников я там посещал (и сопровождал). Так вот. Сначала, приехав, ты несколько часов лежишь на каталке в приёмном покое. Вокруг тебя толчётся человек двадцать больных с разнообразными неизвестными пока болячками. Медсестёр две-три, и на вопрос "сестра, когда же моя очередь" они злобно огрызаются, ибо заебались уже всем повторять, что не знают. Вполне возможна ситуация, когда кто-нибудь подойдёт и вколет тебе что-нибудь, проигнорировав твои робкие вопросы "а что это" и "от чего это" - лежи потом, и думай, не перепутали ли тебя с другим больным. Каталка неудобная, где сортир - неизвестно, поесть не принесут. Потом ты попадаешь в палату, где лежат ещё четыре-пять человек. Один из них непрерывно орёт. Не потому, что ему больно, нет. Просто он сумасшедший. Поэтому никто никаких мер, чтобы он не орал, принимать не будет. Если ты можешь ходить, то ещё один сосед будет с частотой раз в пять минут просить тебя позвать сестру, потому как сам он ходить не может. Если ты не можешь ходить, то никто из соседей для тебя сестру не позовёт. А если позовёт - будет хуже, сестра придёт злобная и уставшая от постоянной беготни по больным. Вонища в палате будет стоять страшная, а если ты попытаешься проветрить помещение, на тебя заорут в несколько глоток, что ты хочешь, чтобы они все поумирали от простуды - что, кстати, недалеко от истины. Поесть ты сможешь; если ты лежачий - тебе принесут. Принесут откровенные помои. Не торопись вставать на ноги - в столовой то же самое. Если у тебя нет родственников, которые готовы каждый день носить тебе еду - твои дела плохи. Именно каждый день, потому что холодильника нет и не будет. Об элементарной вещи типа электрической розетки в палате (хотя бы мобильник зарядить) - не мечтай. И не оставляй мобилу без внимания - в больницах воруют, и много. Если ты совсем плох и вообще ничего не можешь - больница сделает тебе ещё хуже. Сталкивался со случаем, когда больной, простите, обосрался и лежал в собственном дерьме - он физически был не в состоянии что-то исправить, а сестра подходить не торопилась. Не думай, что, заплатив медсестре, ты сможешь избавиться хотя бы от одного недостатка из указанных. Платят ВСЕ. Ну, или, по крайней мере, многие. Они физически не в состоянии обеспечить более-менее полноценный уход каждому. Излишне говорить, что в КОММЕРЧЕСКИХ больницах ничего этого нет. Исключением может стать разве что качество еды - оно и в платных больницах бывает не очень (хотя и получше), но зато холодильник там, как правило, есть. А теперь учти: если ты вызовешь бесплатную скорую, и она обнаружит, что тебе необходима госпитализация - тебя повезут в бесплатную же больницу. В ту, к которой эта скорая приписана. Если ты вызываешь платную скорую - тебя повезут в ту больницу, в которую ты захочешь сам; если твои предпочтения не столь определённы - врач обзвонит больницы, которые тебе подходят, и выяснит, где есть места. Miguel Originally posted on migmit.vox.com 5th February 2009
: On Vox: Объясните, а?
Вот есть хаскельный код (пример упрощён до предела): data P a = P a (forall b. b -> P (a, b))Нормально компилится, если указать прагму LANGUAGE RankNTypes в GHC или ключик -98 в Hugs-е.Можно написать несколько "генераторов" для такого P:sameValue :: a -> P aКак сделать это на C++??? Я попробовал, моего плюс-фу не хватило. Светилы, если вы есть, можете подсказать? Хочется что-то вроде этого:
Не заработает, ибо template и virtual вместе не живут. Убрать virtual нельзя - класс определяется как абстрактный, реализация функции cdr будет разной (см. выше), но эту разницу надо скрыть "под капотом", указывая везде базовый класс.Как? Originally posted on migmit.vox.com 18th January 200913th January 200925th December 2008
: On Vox: Попробовал написать
длинный пост про хаскель. После усушки и утруски осталось следующее: Бездушное программирование - это программирование без блоков do. Причём я даже не уверен, что не слямзил это где-нибудь. Originally posted on migmit.vox.com 17th December 2008
: On Vox: И ещё перевод
Оригинал здесь: http://www.aegisub.net/2008/12/if-progra Если бы языки программирования были религиозными учениями. Идея почерпнута из известного текста "Если бы языки программирования были автомобилями". 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
: On Vox: Записки маньяка
Бэкграунд. Те, кто знает Ruby - пропустите. В рубях есть такая фишка - собственно, основная - как "блоки". Каждому вызову метода можно дать как бы дополнительный параметр - блок кода. Эти блоки не являются настоящими значениями первого класса; однако, внутри метода их можно вызывать. Метод:
Его вызов:
Вот, значит. Команда yield вызовет этот самый блок кода. Замечание: из блока кода можно сделать настоящую лямбда-функцию, так что не говорите, что что-то не так. Дальше - больше. Переданный блок может иметь параметры - скажем, так:
Соответственно, параметры передаются yield:
Ну, и что маньяк в моём лице попытался сделать в первую очередь? Ну разумеется, что-то такое:
С тем, чтобы метод вызвал переданный ему блок, в свою очередь передав этому блоку другой блок кода. И что бы вы думали? Не сработало. Originally posted on migmit.vox.com 9th December 2008
: On Vox: Присоединюсь
к другим приличным людям, вывесившим эту картинку. Originally posted on migmit.vox.com 6th December 20085th December 2008
: On Vox: Начал понимать
что имел в виду Луговский, когда говорил о бедности русского мата в сравнении с английским. Вот, например, из недавно прочитанного: охранник обнаруживает дверь, которую он, вроде как, должен был охранять, взломанной. Охранник: "Shit, shit, shit, fuck!" Ну как это перевести? Чтобы сохранился не только общий смысл, но и скорость (произносится сие очень быстро). Не представляю. Originally posted on migmit.vox.com 29th November 2008
: On Vox: Без особого повода
Уважаемые товарищи Progs.Kiev.ua! В порядке ответа на ваше письмо, пришедшее с ящика noreply@что-то-там, сообщаю: вы пидарасы. Всё остальное (как, например, то, что я НЕ собираюсь вкладывать от 1000 до 50000 долларов в развитие ваших проектов) с очевидностью следует из вышеизложенного. Originally posted on migmit.vox.com 27th November 2008
: On Vox: Розовая пантера
Meglio Stasera from http://migmit.vox.com/ Фран Джеффрис прекрасна.
Originally posted on migmit.vox.com 22nd November 2008
: On Vox: Православное
Не могу понять, как относиться к развязавшейся последнее время истерии вокруг рублёвской иконы. Для тех, кто в танке: РПЦ требует, чтобы им передали икону "Троица" авторства Андрея Рублёва. Музейные работники достаточно аргументированно указывают, что икона прожить вне музея сколько-нибудь долгое время не может. Так вот. С одной стороны, было бы очень приятно, если бы конфликт разрешился в пользу музейщиков, потому как РПЦ очень нуждается в щелчке по носу, а лучше в ударе по голове (последнее, увы, недостижимо даже в воображении). С другой стороны, это ж не здание (где можно хотя бы картошку хранить), не земля - а икона. Предмет культа больных православием межушного нервного узла (с)piggy_toy. Если в результате всех этих пертурбаций она сдохнет - это уже будет солидный вклад в мировую культуру. Поскольку события всегда развиваются по наихудшему сценарию, будет так. Икону передадут в церкву, она там быстро начнёт разрушаться, поэтому её (изобразив добрую волю) передадут обратно в музей, музейные работники сделают чудо и ПОЧТИ восстановят её (то есть, как предмет культа она сохранится), после чего их уволят за это самое "почти". Originally posted on migmit.vox.com 15th November 2008
: On Vox: А с континуэйшенами проще
Опять про ограниченные монады (см. предыдущий пост). Модуль "Suitable" остаётся тем же, что и был раньше, а вот основной тип данных меняется. Что нам, фактически, нужно от значения типа "Restricted m a"? Нам нужно сбиндить его с последовательностью функций (первая - типа "a -> Restricted m b", вторая - "b -> Restricted m c" и т. д.), доведя до значения, для которого осмысленно запихивание в монаду "m", а затем сделать ему "unembed". Ну так мы сделаем это всё за один шаг, и этот самый шаг сделаем значением нашего нового типа. Нам понадобятся existential types. Основной тип данных: В принципе, можно было бы написать "data RestrCont m a where RestrCont :: ...", используя GADT-ы вместо экзистеншиалсов. Преимущество такого подхода в том, что, если вместо класса "Suitable" использовать что-нибудь нормальное, типа класса "Ord", то полученная вещь заработает, например, в Hugs. Объявляем наш тип монадой: В отличие от предыдущего примера, для того, чтобы сделать новый тип инстансом "MonadPlus" его не нужно менять; нужно лишь, чтобы само "m" поддерживало нужные операции. Операции "embed" и "unembed" тоже упрощаются. Первая из них использует только "restrBind" а вторая - только "restrReturn"
Пример (ищите его в предыдущих постах) проходит без изменений. Раньше, дабы соорудить некоторую алгебраическую операцию на "Restricted m" (из такой же алгебраической операции на m), нам нужно было изменить монаду "n" - так из "RestrPlus m" получалось "MonadPlus (Restricted m)". Сейчас это можно сделать, опять таки, проще:
Originally posted on migmit.vox.com 9th November 2008
: On Vox: Ограниченное - часть 3
Всё изложенное ранее - очень хорошо, но относится к одному единственному классу - "Ord". Увы, мы не можем сделать класс параметром чего бы то ни было - тип можем, а вот класс - нет. Однако в Haskell-Café пробежало решение и этой проблемы. Для этого решения требуются type families. Я вынесу в отдельный модуль как общий код, так и то, что нужно написать для "Set": Прагм многовато, но, за исключением первой, GHC подсказывает все остальные. Мы используем "Set" для примера, так что импортируем соответствующий модуль: Суть дела - в следующем классе; функция "constraints" не использует свой аргумент, ей нужен только его тип; все необходимые ограничения на тип "a" содержатся в типе "Constraints m a" - позднее мы увидим, как именно они задаются. Теперь можно ввести собственно "ограниченную монаду" и ограниченный вариант "MonadPlus": Как и раньше, нам пригодится аналог функции "msum": Теперь посмотрим, как это всё работает для "Set". Для начала - класс "Suitable". Как видим, и класс "Suitable" и тип "Constraints" определены лишь тогда, когда "a" относится к классу "Ord". Это и есть то ограничение, которое нам нужно. Функция "constraints" позволяет нам проверять это ограничение, как, например, в определении инстанса "RestrMonad" или инстанса "RestrPlus". Теперь основной модуль выглядит ровно так же, как и раньше, только функции, начинающиеся на "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 Пример тоже выглядит в точности как раньше: Вот и всё. Originally posted on migmit.vox.com
: On Vox: Ограниченное - часть 2
В предыдущем решении один и тот же, по существу, код повторялся дважды. Это не есть хорошо. Вполне можно написать то же самое ОДИН раз, используя "Identity" в первом случае и "[]" во втором. Ну и, интуиция подсказывает, что нужно использовать тот факт, что и то, и другое является монадой. Нам понадобится чуть больший список импортов: Тип данных мы определим так же, как и "OrdP" ранее, только вместо "[]" у нас будет произвольное "n". Снова так же определяется инстанс "Monad". Теперь, "OrdT m n" является монадой, если "n" является монадой. То есть, "OrdT m" - это такой преобразователь монад. Трансформер, иначе говоря. Для полноты картины можно объявить и инстанс "MonadTrans". Я не знаю, зачем это может понадобиться, но пусть уж будет, хотя бы для красоты. Функция "embed" выглядит как и раньше, а вот "unembed" - не совсем. В предыдущем посте "unembed" и "unembedPlus" различались - в unembedPlus участвовала функция "ordMSum". Поскольку её аналог для произвольного "n" заранее неизвестен, придётся передавать её в "unembed" как параметр. Ну, теперь несложно воспроизвести старый "OrdM": Далее, если "n" - не просто монада, но инстанс "MonadPlus" (как, скажем, "[]"), то "OrdT m n" - тоже инстанс "MonadPlus"; кроме того, если "m" - инстанс "OrdMonadPlus", то можно воспроизвести старое "unembedPlus": Наш пример теперь выглядит так:
Originally posted on migmit.vox.com
: On Vox: Ограниченное - часть 1
Некоторое время тому назад уважаемый Что такое, собственно, ограниченная монада? Это то же самое, что и обычная монада, но имеющая смысл только тогда, когда тип, который она содержит, относится к некоторому классу. Характерный пример - множество, "Set". Мы не можем образовать "Set a", если "a" не относится к классу "Ord". В то же время, "Set" не так уж сильно отличается от "[]", и можно ожидать, что стандартный "do"-синтаксис окажется полезным и здесь. Однако, из-за указанного ограничения, превратить "Set" в монаду нельзя. То, что я сказал, можно превратить в описание класса "OrdMonad" и его инстанса для "Set":
Далее, в соответствии с тем, что сделано для монады "[]", можно также объявить аналог "MonadPlus":
В дальнейшем нам пригодится аналог функции "msum": Как же заставить "do"-синтаксис работать с такими "ограниченными монадами"? Ну, один из вариантов - использовать Template Haskell и руками преобразовать do-нотацию в выражение с "ordReturn" и "ordBind". Однако, использовать TH - значит признать своё поражение; мы поищем другой способ. Один из способов сделать это внутри хаскеля описан в посте, на который выше стоит ссылка. Желающие могут посмотреть, как это сделано; я же изложу собственный вариант. Для начала нам потребуются GADT-ы (без них, увы, никуда, разве что existential types вспомнить, а это то же самое). Последний импорт - это импорт того модуля, который приведён выше; он содержит, в частности, класс "OrdMonad". Тип данных, который мы будем использовать, содержит, собственно, два конструктора - аналоги >>= и return. Однако, поскольку цепочка >>= будет всегда начинаться с чего-то, относящегося к классу "Ord", мы можем ограничить один из типов: Теперь несложно объявить этот тип монадой. При этом мы будем использовать монадические законы - например, то, что "return x >>= f = f x". Именно эти законы предопределяют определение ">>=" для нашего типа "OrdM". Далее, нужно найти способ перегнать нашу "ограниченную монаду" в настоящую: и обратно: Уже неплохо. Однако, нам нужно работать ещё и с "OrdMonadPlus". Увы, даже если есть инстанс "OrdMonadPlus" для "m", превратить "OrdM m" в инстанс "MonadPlus" не удаётся. Решение по ссылке выше - добавить в "OrdM" парочку конструкторов, скажем, "OrdMZero" и "OrdMPlus". Мне это решение не нравится, так как не учитывает законы, связанные с "mzero" и "mplus" - например, ассоциативность. Вместо этого я буду просто хранить список, элементы которого - по сути те же, что и раньше. "mzero" превратится в пустой список, а "mplus" - в конкатенацию.
Для "OrdP" я объявлю - по сути, точно так же - инстанс класса "Monad": Правда, разбор случаев пришлось упаковать во вспомогательную функцию "handleOmx"; заметьте, что в выражении ">>= handleOmx" оператор ">>=" - это ">>=" для монады-списка, то есть, по сути, "concatMap". Теперь можно определить и инстанс "MonadPlus": Наконец, нам понадобятся варианты функций "embed" и "unembed" для "OrdP":
Это те же определения, что и для "OrdM", только опять-таки разбор случаев запихнут в ">>= handleOmx" Теперь можно писать такие, например, вещи:
Запускаем, получаем:
Originally posted on migmit.vox.com 30th October 2008
: On Vox: Счёт за телефон
и как на него следует реагировать: Девушка здесь - Джейн Фонда. Originally posted on migmit.vox.com 10th October 2008
: On Vox: Шекспир
Никогда не был большим поклонником его сонетов. Однако, когда один из них читает гениальный актёр - даже меня пронимает. Originally posted on migmit.vox.com 5th October 2008
: On Vox: Гум-манитарии
avva призвал желающих присоединиться к нему в изучении латыни. OK, я решил попробовать. Прочитал инструкцию. Согласно прочитанному, подписался на их список рассылки (ДО ФИГА ненужного хлама) и написал ведущему курс. В ответ - гробовое молчание. Ждал некоторое время, сегодня утром решил, что хватит. Отписался от рассылки. После этого пришло письмо от ведущего. С первым заданием. И указанием, к какому сроку его надо выполнить. Ну, вы поняли. Вчера. В этом письме были указаны несколько адресов страниц, посвящённых его курсу. Часть не работает, часть работает, но относится к курсу за прошлый год. Sorry, guys, but I give up. Originally posted on migmit.vox.com
: On Vox: МИГМиТ
Международный Институт Гостиничного Менеджмента и Туризма. Originally posted on migmit.vox.com 4th October 2008
: On Vox: Ох, какой срач развели!
На тему "ФП versus ООП". Кому интересно - заходите на огонёк: http://ady-1981.livejournal.com/16012.ht Originally posted on migmit.vox.com 25th September 2008
: On Vox: JavaScript
Таки нарвался. Originally posted on migmit.vox.com 24th September 2008
: On Vox: Вечерние разговоры
Собеседник - Считаем у этого сигнала энергетический спектр... M - Предположим, что в последовательности бит комбинация "01" встечается в четыре раза чаще, чем комбинация "10"... Только через минуту дошло, что глупость сморозил. Originally posted on migmit.vox.com 22nd August 2008
: On Vox: MPlayer
Может, это я такой тормоз, но раньше я такого прикола не замечал. Качал видео в wmv через Everything done. Thank you for downloading a media file containing proprietary and patented technology. Originally posted on migmit.vox.com 9th August 2008
: On Vox: HJKL
Во всех учебниках по ViM-у эти клавиши указаны если не в самом начале, то близко. И ни разу я не смог их запомнить - потому как стрелки всегда под рукой. Originally posted on migmit.vox.com
: On Vox: Наваждение какое-то
Похоже, компьютеры меня боятся. Для счастливчиков поясняю: сие есть Windows 3.1 (или родственница).
После этого, к моему облегчению, снова загрузилась обычная оболочка. Originally posted on migmit.vox.com 26th July 2008
: On Vox: Прогулки с телефоном
Телефон, как известно, служит для того, чтобы фотографировать. Я этим не слишком увлечён, но оно таки копится. Ну и вот.
Для затравки, стандартный сюжет: банковский терминал вылетает в интерфейс виндов. Здесь, для разнообразия, он вылетел дальше: Ближе я подойти не успевал, щёлкал через стекло. Развивая тему: видимо, кого-то сильно достали спамеры: Все знают, что такое "успешный менеджер", но никто его не видел. А вот: С какого количества песчинок начинается куча - неизвестно до сих пор. В основном из-за недостатка терпения. Но вот с какого количества веб-страниц становится МНОГО - известно. Правда, единица измерения странная: Нет, я не боюсь сделать рекламу этому придурку. Здесь же уместно помянуть информацию о стоимости сайта (и пусть А. Лебедев удавится от зависти): Следующее напоминает известный анекдот про то, как полезно изучать английский в СПбГУ: Честно говоря, я затруднюсь это перевести обратно на русский. Ну и, чтобы (временно) закрыть тему - три, а точнее, шесть, объявлений, повергающих меня в ступор - кому верить? Вам нужно алиби? Вам сюда: Других вывесок там не было, и я так и не узнал, что же это на самом деле такое. Наши начинающие - всем начинающим начинающие: Интересно, что фотографировал я это в Публичке (или как там её сейчас). В городе появились навозные жуки-гиганты: Эти, что ли? Originally posted on migmit.vox.com 22nd June 2008
: On Vox: Мастера
и Маргариты. Originally posted on migmit.vox.com 15th June 2008
: On Vox: Не забыть:
Чтобы скачать что-то с mihd.net нужно дождаться, пока всё устаканится, и из файербага выполнить функцию ticket.detected(). Тогда появится надпись "Download", котороая и есть ссылка на файл. Иначе на заработает. Originally posted on migmit.vox.com |
|