Miguel

Recent Entries

You are viewing the most recent 50 entries.

7th May 2008

19:45: On Vox: Интересно...

...сегодняшние ритуальные действа как-нибудь связаны с предстоящей через неделю коронацией Каспиана X?

И ещё интересно: в последовательности Мираз-Каспиан наша последовательность "лысый-волосатый" соблюдается? К., вроде, волосатый (и даже очень).

Originally posted on migmit.vox.com

6th May 2008

22:43: On Vox: Поперхнулся бубликом

[info]ifrau: да, хаскелл — вовсе не эзотерический язык, но лично мне понадобится уйма времени, чтобы его освоить, поскольку в основном я пишу на языках высокого уровня
А потом последовало феерическое:
я пишу на delphi, pascal — язык высокого уровня
haskell — низкого

Originally posted on migmit.vox.com

1st May 2008

20:15: On Vox: Длинное, про Хаскель - часть 2

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

> data StateA = StateA (Maybe String) [StateA] deriving Show

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

> data A a b = A ((StateA -> a) -> (StateA, Maybe [Bool]) -> (StateA, Maybe [Bool])) ((StateA -> a) -> (StateA -> b))

Значения типа Maybe [Bool] понадобятся нам для отслеживания, в какой части стрелки был задан вопрос. В частности, значение Nothing на входе сигнализирует о том, что работа только что началась, а на выходе - о том, что работа окончена и можно забирать выходное значение.

Как и для монад, мы соорудим "клиента" для пользователя.

> runA :: A a b -> (a -> IO b)
> runA (A t f) x = runA' (StateA Nothing [], Nothing) where
>     h (StateA Nothing []) = x -- функция, по начальному состоянию (StateA Nothing []) устанавливающая значение x.
>     runA' c =
>         case t h c of
>           (s, Nothing) -> return $ f h s
>           (StateA (Just str) ss,Just bs) ->
>               do putStrLn str
>                  putStrLn $ "debug: " ++ show (ss,bs) -- эта строка тоже стоит здесь исключительно в целях отладки.
>                  putStr ">>> "
>                  str' <- getLine
>                  runA' (StateA (Just str') ss,Just bs)

Объявим нашу стрелку стрелкой.

Во-1, стрелка arr f не задаёт пользователю никаких вопросов и не выслушивает никаких ответов.

Во-2, стрелка A1 >>> A2 выполняет вначале A1 - приписывая к каждому вопросу False (функцией liftM (False:)) для индикации того, что выполняется первая стрелка - после чего выполняет A2, приписывая к каждому вопросу True. Когда же от пользователя приходит ответ, то первая часть в Maybe [Bool] используется для определения того, был ли задан вопрос внутри A1 или внутри A2, после чего убирается функцией liftM tail.

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

> instance Arrow A where
>     arr f = A (\_ (s, Nothing) -> (s, Nothing)) (\h s -> f $ h s)
>     ~(A t1 f1) >>> ~(A t2 f2) = A t $ f2 . f1
>         where
>           secondPart h p = let (s, mbs) = t2 (f1 h) p in (s, liftM (True:) mbs)
>           t h (s, Just (True:bs)) = secondPart h (s, Just bs)
>           t h (s, mbs) =
>               case t1 h (s, liftM tail mbs) of
>                 (s', Nothing) -> secondPart h (s', Nothing)
>                 (s', Just bs) -> (s', Just $ False:bs)
>     first ~(A t f) = A t' f'
>         where
>           t' h (s@(StateA str ss), mbs) =
>               let (s1, s2) =
>                       case mbs of
>                         Nothing -> (s,s)
>                         Just _ -> (StateA str $ tail ss, head ss)
>                   (StateA str' ss', mbs') = t (fst . h) (s1, mbs)
>               in (StateA str' (s2:ss'), mbs')
>           f' h (StateA str (s:ss)) = (f (fst . h) $ StateA str ss, (snd . h) s)

В принципе, в first открыта дорога для оптимизаций - например, first (first ...) будет сохранять исходное состояние ДВАЖДЫ, что излишне - но эти оптимизации для дальнейшего несущественны.

Как и раньше, нам нужен строительный блок - стрелка, задающая пользователю вопрос, и возвращающая ответ.

> askA = A t $ const $ \(StateA (Just str) _) -> str
>     where
>       t h (s, Nothing) = (StateA (Just $ h s) [], Just [])
>       t h (s, Just []) = (s, Nothing)

И снова тестируем. На сей раз, мы будем передавать в каждый следующий запрос только ту информацию, которая ему нужна.

> testA = arr (\() -> "1?") >>> askA >>>
>         arr (\a1 -> "You said: " ++ a1 ++ "; 2?") >>> askA >>>
>         arr (\a2 -> "You said: " ++ a2 ++ "; 3?") >>> askA >>>
>         arr (\a3 -> "You said: " ++ a3 ++ "; 4?") >>> askA >>>
>         arr (\a4 -> "You said: " ++ a4 ++ "; 5?") >>> askA >>>
>         arr (\a5 -> "You said: " ++ a5) >>> askA >>>
>         arr (\_ -> ())

Проверяем:

*Test> runA testA ()
1?
debug: ([],[True,False])
>>> a
You said: a; 2?
debug: ([],[True,True,True,False])
>>> b
You said: b; 3?
debug: ([],[True,True,True,True,True,False])
>>> c
You said: c; 4?
debug: ([],[True,True,True,True,True,True,True,False])
>>> d
You said: d; 5?
debug: ([],[True,True,True,True,True,True,True,True,True,False])
>>> e
You said: e
debug: ([],[True,True,True,True,True,True,True,True,True,True,True,False])
>>>

Всё отлично работает. Конечно, довольно много передаётся как Maybe [Bool], но это тип сам по себе маленький, и несколько бит нас не волнуют. Главное, предыдущие ответы пользователя НЕ протаскиваются через всю последовательность вопросов-ответов.

А теперь напишем примерно то же самое в стрелочном do-синтаксисе:

> testA' =
>     proc () ->
>         do a1 <- askA -< "1?"
>            a2 <- askA -< "You said: " ++ a1 ++ "; 2?"
>            a3 <- askA -< "You said: " ++ a2 ++ "; 2?"
>            a4 <- askA -< "You said: " ++ a3 ++ "; 2?"
>            a5 <- askA -< "You said: " ++ a4 ++ "; 2?"
>            askA -< "You said: " ++ a5
>            returnA -< ()

Проверяем:

*Test> runA testA' ()
1?
debug: ([StateA Nothing []],[True,False,True,False,True])
>>> a
You said: a; 2?
debug: ([StateA (Just "a") [StateA Nothing []]],[True,True,False,True,False,True])
>>> b
You said: b; 2?
debug: ([StateA (Just "b") [StateA (Just "a") [StateA Nothing []]]],[True,True,True,False,True,False,True])
>>> c
You said: c; 2?
debug: ([StateA (Just "c") [StateA (Just "b") [StateA (Just "a") [StateA Nothing []]]]],[True,True,True,True,False,True,False,True])
>>> d
You said: d; 2?
debug: ([StateA (Just "d") [StateA (Just "c") [StateA (Just "b") [StateA (Just "a") [StateA Nothing []]]]]],[True,True,True,True,True,False,True,False,True])
>>> e
You said: e
debug: ([StateA (Just "e") [StateA (Just "d") [StateA (Just "c") [StateA (Just "b") [StateA (Just "a") [StateA Nothing []]]]]]],[True,True,True,True,True,True,False,True,False,True])
>>>

Упс. Работать-то оно работает, но... мы снова получаем ту же проблему, что и с монадами. Старые, ненужные ответы пользователя снова начали протаскиваться через всю цепочку. Почему? Потому что препроцессор стрелочного синтаксиса глуп. Нет особых проблем преобразовать testA' в testA автоматически. Увы, но даже при помощи Template Haskell это не сделать - Template Haskell не знает о стрелочном do-синтаксисе ничего. Нужен внешний препроцессор.

Резюме. Это - одна из тех вещей, за которые я не люблю стрелки. Уж слишком в их реализации виден подход "абы как, лишь бы работало".

Originally posted on migmit.vox.com

20:14: On Vox: Длинное, про Хаскель - часть 1

   


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

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

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

> {-# LANGUAGE Arrows #-}

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

> module Test where
> import Control.Arrow
> import Control.Monad
> import Data.Either
> import Data.Maybe

Пока ничего интересного.

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

> data StateM = StateM String [Maybe StateM] deriving Show

Желающие сообщить мне, что тут я заново изобрёл Лисп, могут не трудиться. Я, типа, знаю.

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

> newtype M a = M (Maybe StateM -> Either StateM a)

Для удобства придумаем своего рода "клиента" для пользователя - функцию, которая возьмёт на себя задачу следить за сохранностью State.

> runM :: M a -> IO a
> runM mx = runM' Nothing where
>     runM' s =
>         case mx $: s of
>           Left (StateM str ss) ->
>               do putStrLn str
>                  putStrLn $ "debug: " ++ show ss -- эта строка здесь стоит исключительно в целях отладки. Её можно убрать, не нарушив основную функциональность "клиента".
>                  putStr ">>> "
>                  str' <- getLine
>                  runM' $ Just $ StateM str' ss
>           Right x -> return x

Для простоты, я введу оператор, аналогичный ($), работающий с нашими функциями.

> infixr 0 $:
> M t $: s = t s

Теперь объявим нашу монаду монадой.

> instance Monad M where

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

>     return x = M $ \Nothing -> Right x

Ситуация с bind интереснее. Выполняя mx >>= f, мы, конечно, должны сначала выполнить mx и получить x. Затем, мы должны выполнить f x. Но, выполняя f x, мы должны каждый раз запоминать значение x, чтобы в следующий раз, когда придёт ответ пользователя, дёрнуть именно f x, а не что-нибудь вроде f (_|_). Поэтому, мы должны запомнить последний State, после которого mx вернула значение, и использовать его для вычисления x снова и снова.

Конечно, если mx вообще никак с пользователем не связывается, мы можем всех этих "запоминаний" не делать.

>     mx >>= f =
>         case mx $: Nothing of
>           Right x -> f x
>           _ -> M t'
>         where

Функция handle выполняет основную работу. У неё есть два параметра (типа Maybe StateM). Первый из них передаётся в mx, второй - в (f x), если, конечно, mx завершила работу и вернула x. Заметим, что, если mx не вернула значение, handle запоминает этот факт, приписывая Nothing к стеку состояний - чтобы не перепутать в следующий раз. Если же mx вернула значение, handle приписывает к стеку состояний то состояние, которое было передано mx - чтобы в следующий раз его вспомнить.

>           handle toFirst toSecond =
>               case mx $: toFirst of
>                 Left (StateM str ss) -> Left $ StateM str $ Nothing:ss
>                 Right x ->
>                     case f x $: toSecond of
>                       Left (StateM str ss) -> Left $ StateM str $ toFirst:ss
>                       Right y -> Right y

Наконец, mx >>= f, начав работу, передаёт сигнал начать работу mx, а затем, если надо, и f x (это на случай, если mx завершится, а не потребует задать пользователю вопрос). Приняв ответ от пользователя, она для начала определяет, был ли вопрос задан внутри mx или внутри f x. Для этого, она смотрит на первый элемент в стеке состояний. Если этот первый элемент - Nothing, значит, вопрос был задан внутри mx, и надо запустить её заново, сообщив ей ответ пользователя, а в f x передать сигнал начала работы (если mx завершится и выдаст значение x). Если же первый элемент - некоторое состояние, значит, вопрос был задан внутри f x. Поэтому, нужно по этому первому элементу определить x - для чего придётся снова запустить mx - а затем снова запустить f x, передав этой функции ответ пользователя.

>           t' Nothing = handle Nothing Nothing
>           t' (Just (StateM str (Nothing:ss))) = handle (Just $ StateM str ss) Nothing
>           t' (Just (StateM str (s:ss))) = handle s $ Just $ StateM str ss

Не помешает и строительный кирпичик - функция, задающая вопрос пользователю, и возвращающая ответ.

> askM s = M ask' where
>     ask' Nothing = Left $ StateM s []
>     ask' (Just (StateM str [])) = Right str

Напишем тестовую функцию, в духе того, что предлагалось в SPbHUG:

> testM =
>     do a1 <- askM "1?"
>        a2 <- askM $ "You said: " ++ a1 ++ "; 2?"
>        a3 <- askM $ "You said: " ++ a2 ++ "; 3?"
>        a4 <- askM $ "You said: " ++ a3 ++ "; 4?"
>        a5 <- askM $ "You said: " ++ a4 ++ "; 5?"
>        askM $ "You said: " ++ a5
>        return ()

Думаю, смысл этой функции всем понятен. Запускаем:

*Test> runM testM
1?
debug: [Nothing]
>>> a
You said: a; 2?
debug: [Just (StateM "a" []),Nothing]
>>> b
You said: b; 3?
debug: [Just (StateM "a" []),Just (StateM "b" []),Nothing]
>>> c
You said: c; 4?
debug: [Just (StateM "a" []),Just (StateM "b" []),Just (StateM "c" []),Nothing]
>>> d
You said: d; 5?
debug: [Just (StateM "a" []),Just (StateM "b" []),Just (StateM "c" []),Just (StateM "d" []),Nothing]
>>> e
You said: e
debug: [Just (StateM "a" []),Just (StateM "b" []),Just (StateM "c" []),Just (StateM "d" []),Just (StateM "e" []),Nothing]
>>>

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

Можно переписать этот пример в соответствии с тем, что предлагалось в SPbHUG.

> testM' =
>     do a5 <- do a4 <- do a3 <- do a2 <- do a1 <- askM "1?"
>                                            askM $ "You said: " ++ a1 ++ "; 2?"
>                                   askM $ "You said: " ++ a2 ++ "; 3?"
>                          askM $ "You said: " ++ a3 ++ "; 4?"
>                 askM $ "You said: " ++ a4 ++ "; 5?"
>        askM $ "You said: " ++ a5
>        return ()

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

*Test> runM testM'
1?
debug: [Nothing,Nothing,Nothing,Nothing,Nothing]
>>> a
You said: a; 2?
debug: [Nothing,Nothing,Nothing,Nothing,Just (StateM "a" [])]
>>> b
You said: b; 3?
debug: [Nothing,Nothing,Nothing,Just (StateM "b" [Just (StateM "a" [])])]
>>> c
You said: c; 4?
debug: [Nothing,Nothing,Just (StateM "c" [Just (StateM "b" [Just (StateM "a" [])])])]
>>> d
You said: d; 5?
debug: [Nothing,Just (StateM "d" [Just (StateM "c" [Just (StateM "b" [Just (StateM "a" [])])])])]
>>> e
You said: e
debug: [Just (StateM "e" [Just (StateM "d" [Just (StateM "c" [Just (StateM "b" [Just (StateM "a" [])])])])]),Nothing]
>>>

Продолжение следует.

Originally posted on migmit.vox.com

22nd April 2008

01:41: On Vox: Обожаю ЛОР

Только там можно от искренне верующего услышать фразу "верующий должен есть христа постоянно"...

Originally posted on migmit.vox.com

18th April 2008

21:53: On Vox: Мой приход к богу

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



Я возвращался домой из Уни. Тишину ночи нарушало лишь позвякивание моего велосипеда. Маленький городок, похоже, уже спал, оставив улицу для полуночников, подобных мне. Я лениво размышлял о построении особо хитрой SQL-таблицы, когда, совершенно неожиданно, заметил небольшую церковь сбоку от дороги.

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

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

Я слез с велосипеда и направился в крытый дворик, в глубине которого стояла поразившая моё воображений скульптура. Какое-то умиротворение разливалось в моей душе. При моём приближении двор церкви озарился тем же неярким светом, и я увидел, что справа, ближе к стене, стоит высокий квадратный столб, из которого, в разных направлениях, торчат четыре x%я в положении "товсь" и струячат на камни.

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

Вот так я пришёл к Богу. Но, к сожалению, не застал.

Originally posted on migmit.vox.com

13th April 2008

17:38: On Vox: Подоконное

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

Originally posted on migmit.vox.com

12th April 2008

01:57: On Vox: Рецепт

Берём интересную, в меру популярную пьесу, по которой однажды был поставлен великолепный фильм, обязанный своим качеством двум блестящим актёрам.
Отдаём пьесу на откуп современному драматургу, который превращает чётко выстроенный сюжет в невнятную кашу.
Приглашаем дизайнера, который впечатлился работами фирмы Apple, но не понял, за счёт чего они получаются хороши. Он строит футуристический дом, от интерьеров которого хочется блевать.
Берём одного блестящего актёра. Даём ему напарника, неумение которого играть блокирует все попытки таланта блестящего прорваться наружу.
Приправляем однообразной и усыпляющей музыкой.
Ставим с этими ингредиентами телеспектакль со всеми его атрибутами - такими как, скажем, ненатуральный звук.
И наконец - КАТАРСИС! Когда зритель отчаялся увидеть хоть что-то пристойное - убиваем мудака, испортившего всё, что ещё оставалось приличного, и даём блестящему актёру несколько секунд поиграть в одиночестве. Зритель в восторге. На экране финальные титры заглавными буквами (нет, прав был Тёма Лебедев: нельзя писать текст сплошным капсом) - SLEUTH.
Это всё, конечно, если не считать одной маленькой вещи: глядя на то, как главные герои передают друг другу пульт управления всей электроникой в этом несуразном доме, я неприлично ржал. Потому что у меня в это время в руке удобно лежал ТОЧНО ТАКОЙ ЖЕ ПУЛЬТ! Только это был пульт дистанционного управления айМаком. Эта деталь дизайна Apple так впечатлила создателей фильма, что они оставили её абсолютно без изменений.

Originally posted on migmit.vox.com

2nd April 2008

00:57: On Vox: А-а-а-а!

Кремль заказывает 3200 мышей. Зачем - не известно.

Кремлёвская охрана (спецподразделение, охраняющее президента Путина) заказала 3200 белых мышей.
Общий объём закупок держится в секрете. Известно только, что требовались мыши женского пола и весом не более 18 грамм каждая.
Официальный источник сообщил: "все удивляются, зачем они нужны. Но раз их заказали - значит, они требуются". Тот же источник отказался высказать предположения о предназначении мышей, сказав, что "у него и без этого есть, над чем подумать".
Всё это очень странно. Разумеется, есть некоторые правдоподобные предположения: возможно, армия преданных грызунов является естественным ответом на многочисленные угрозы со стороны окружающих Кремль слонов? Или мыши будут принимать участие в тестировании ядовитых и опасных химикатов? А, может быть, они просто предназначены для кормления орлов, содержащихся в Кремле для отпугивания ворон?
В любом случае, по информации агентства Рейтер, Кремль уже нашёл поставщика и готов заплатить за них 475776 рублей ($20000). Сможет ли кто-нибудь побить этот рекорд?

Originally posted on migmit.vox.com