Покритикуйте калькулятор на Haskell
От: Кодёнок  
Дата: 20.11.07 08:28
Оценка:
Задача: интерактивный (read-eval loop) калькулятор с поддержкой присваивания переменным, приоритетами и ассоциативностью. Парсер принципиально с нуля.

Что можно улучшить?

Я как-то боюсь добавлять сюда обработку ошибок — кажется оно станет совсем нечитаемым тогда.

import IO
import Char
import Data.Map

main = do hSetBuffering stdout NoBuffering
          readEvalLoop Data.Map.empty

readEvalLoop env = do putStr ">>> "
                      s <- getLine
                      (result,newenv) <- return $ eval s env 
                      putStr result
                      readEvalLoop newenv

eval s env = case (tokenize s) of
             [] -> ("",env)
             ts -> let (rv,newenv) = (calculate ts env) in (show rv ++ "\n", newenv)

tokenize :: String -> [Token]
tokenize []                           = []
tokenize (c:cs) | isSpace(c)          = tokenize cs
                | c `elem` "=+-/*^()" = (Symbol c):(tokenize cs)
                | isDigit(c)          = readNumber [c] cs
                | isAlpha(c)         = readIdent [c] cs

readNumber num []                     = [Number (read num)]
readNumber num s@(c:cs) | c == '.'    = readDigits (num ++ [c]) cs
                        | isDigit(c)  = readNumber (num ++ [c]) cs
                        | True        = (Number (read num)):(tokenize s)

readDigits num []                     = [Number (read num)]
readDigits num s@(c:cs) | isDigit(c)  = readDigits (num ++ [c]) cs
                        | True        = (Number (read num)):(tokenize s)

readIdent name []                     = [Ident name]
readIdent name s@(c:cs) | isAlphaNum(c) = readIdent (name ++ [c]) cs
                        | otherwise   = (Ident name):(tokenize s)

data Token = Number Float
           | Symbol Char
           | Ident String

calculate [Number x] env = (x, env)
calculate ts env = case (reduce ts [0] env) of
                   (val, [], newenv) -> (val, newenv)

reduce :: [Token] -> [Integer] -> Data.Map.Map String Float -> (Float, [Token], Data.Map.Map String Float)
reduce [Number x] (p:ps) env = (x, [], env)
reduce (Symbol '(' : rest) (p:ps) env = reduce rest (0:p:ps) env
reduce (Number x : Symbol ')' : rest) (p:ps) env | p == 0 = reduce (Number x : rest) ps env
                                                 | p > 0  = (x, Symbol ')' : rest, env)
reduce (Symbol '-' : rest) (p:ps) env = let (x, zs, newenv) = reduce rest (priority 'u':ps) env
                                        in reduce (Number (-x) : zs) (p:ps) newenv
reduce (Number x : Symbol op : rest) (p:ps) env = if priority op < p || not (isRightAssoc op) && priority op == p
    then (x, Symbol op : rest, env)
    else let (y,zs,newenv) = reduce rest (priority op : ps) env
         in reduce (Number (arithmetic x op y) : zs) (p:ps) newenv
reduce (Ident v : Symbol '=' : rest) (p:ps) env = if priority '=' < p 
    then case Data.Map.lookup v env :: Maybe Float of
         Just x -> (x, Symbol '=' : rest, env) -- this is impossible, but let's handle it anyway
    else let (y,zs,ne) = reduce rest (priority '=' : ps) env
             newenv = Data.Map.insert v y ne
         in reduce (Ident v : zs) (p:ps) newenv
reduce (Ident v : rest) pps env = case Data.Map.lookup v env :: Maybe Float of
                                  Just x -> reduce (Number x : rest) pps env

priority op = case op of
              '=' -> 1
              '+' -> 2 ; '-' -> 2
              '*' -> 3 ; '/' -> 3
              'u' -> 4 -- unary minus
              '^' -> 5

isRightAssoc op | op == '^' = True
                | otherwise = False

arithmetic :: Float -> Char -> Float -> Float
arithmetic x '+' y = x + y
arithmetic x '-' y = x - y
arithmetic x '*' y = x * y
arithmetic x '/' y = x / y
arithmetic x '^' y = x ** y
Re: Покритикуйте калькулятор на Haskell
От: lomeo Россия http://lomeo.livejournal.com/
Дата: 20.11.07 12:03
Оценка: 32 (3)
Здравствуйте, Кодёнок, Вы писали:

Кё>Задача: интерактивный (read-eval loop) калькулятор с поддержкой присваивания переменным, приоритетами и ассоциативностью. Парсер принципиально с нуля.


Неиспользование стандартных библиотек — важно?

Кё>Что можно улучшить?


Обычно, когда видишь функции вида s->(a,s) лучше всего завернуть их в состояние. Код будет гораздо чище. Т.к. у тебя используется IO, то это будет трансформер состояния над IO:

type Env = Map String Float
type Result = String
type CalcS a = StateT Env IO a

readEvalLoop :: CalcS Result
eval :: String -> CalcS Result
calculate :: [Token] -> CalcS Float
reduce :: [Token] -> [Integer] -> CalcS (Float, [Token])


В этом случае код вида

let (x,newenv) = foo z env in (bar x, newenv)


превращается в

x <- foo z
return (bar x)


или, что то же самое

liftM bar (foo z)


Например, у тебя это можно найти в eval и reduce. Во-вторых, но это вопрос стиля, я пытаюсь писать более декларативный код, нежели expression-код. Поэтому код вида

foo = if blah then branchT else branchE
bar = case foobar of { pattern -> expr }


у меня выглядит как

foo | blah      = branchT
    | otherwise = branchE

bar = expr
    where
            pattern = foobar


Мне кажется это гораздо выразительнее. Но, повторю, это скорее дело пристрастий.
Вот пример (сравни со своим eval и calculate):

eval :: String -> CalcS Result
eval s
    | null tokens = return ""
    | otherwise   = liftM show (calculate tokens)
    where
        tokens = tokenize s

calculate :: [Token] -> CalcS Float
calculate [Number x] = return x
calculate ts         = liftM fst (reduce ts [0])


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

Поэтому, парсинг чисел, идентификаторов, определение приоритетов, выполнение операция лучше возложить на плечи того кода, который для этого предназначен.

ident = many1 alphaNum

number = do n <- integer
            f <- fraction
                        return (fromInteger n + f)

integer = liftM toNum (many1 digit)
    where
            toNum = foldl (\x d -> 10*x + toInteger (digitToInt d)) 0

fraction = option 0.0 $ try $ do
                char '.'
                liftM toFrac (many1 digit)
        where
                toFrac = foldr (\x d -> (x + fromIntegral (digitToInt d))/10.0) 0


или даже

parser = makeTokenParser haskellStyle
ident = identifier parser
number = float parser
string = stringLiteral parser


И для приоритетов

buildExpressionParser
        [[op "*" (*) AssocLeft], [op "/" (/) AssocLeft],
         [op "+" (+) AssocLeft], [op "-" (-) AssocLeft]
    where
        op s f assoc = Infix (do{ string s; return f}) assoc


Здесь про парсек: http://legacy.cs.uu.nl/daan/download/parsec/parsec.html

С parsec-ом и StateT IO должно получиться кратко и красиво. Да там по моему уже и StateT не понадобится
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[2]: Покритикуйте калькулятор на Haskell
От: Gaperton http://gaperton.livejournal.com
Дата: 20.11.07 13:54
Оценка:
Здравствуйте, lomeo, Вы писали:


L>В этом случае код вида


L>
L>let (x,newenv) = foo z env in (bar x, newenv)
L>


L>превращается в


L>
L>x <- foo z
L>return (bar x)
L>


Прикольный язык ваш Хаскель, все-таки. Вот интересно — что для такого фокуса обязательно в языке иметь? Можно подобное умутить для строгого языка со скобочной записью вызовов функций?
Re[3]: Покритикуйте калькулятор на Haskell
От: deniok Россия  
Дата: 20.11.07 14:03
Оценка:
Здравствуйте, Gaperton, Вы писали:

G>Прикольный язык ваш Хаскель, все-таки. Вот интересно — что для такого фокуса обязательно в языке иметь?


Тихим шепотом: монады
Re[2]: Покритикуйте калькулятор на Haskell
От: Кодёнок  
Дата: 20.11.07 14:17
Оценка:
Здравствуйте, lomeo, Вы писали:
L>В этом случае код вида
L>let (x,newenv) = foo z env in (bar x, newenv)
L>превращается в
L>x <- foo z
L>return (bar x)

Прикольно

А если туда еще обработку ошибок завернуть, т.е. Result это допустим Either String Float, как это будет выглядеть?
Re[3]: Покритикуйте калькулятор на Haskell
От: lomeo Россия http://lomeo.livejournal.com/
Дата: 20.11.07 14:19
Оценка: +1
Здравствуйте, Gaperton, Вы писали:

G>Прикольный язык ваш Хаскель, все-таки. Вот интересно — что для такого фокуса обязательно в языке иметь? Можно подобное умутить для строгого языка со скобочной записью вызовов функций?


Увы, это не самая стройная часть Хаскеля. do-синтаксис — это syntax sugar для монад, т.е. синтаксис языка заточен под то, что на нём написано

Мой пример

x <- foo z
return (bar x)


разворачивается так:

(foo z) >>= (\x -> return (bar x))


Если в качестве >>= и return мы подставим реализацию их в монаде State, (и выведем из под конструкторов данных State) то получим тот самый let (x,newenv) = foo z env in (bar x, newenv)

В принципе, это неплохо выглядит и без самого сахара:

foo z >>= \x ->
return (bar x)


Т.е. цепочка просматривается. Если под скобочной записью ты понимаешь foo(x,y) то это будет один в один (на javascript, лямбды есть, операторов нет, карринга нет, населена роботами):

bindS(
    function(x) { return foo(z, x) },
        function(x) { return (returnS (bar(x))) } 
)


По моему, по уродски, а я ведь тут ещё конструктор State не использую.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[3]: Покритикуйте калькулятор на Haskell
От: palm mute  
Дата: 20.11.07 14:20
Оценка:
Здравствуйте, Gaperton, Вы писали:

G>Прикольный язык ваш Хаскель, все-таки. Вот интересно — что для такого фокуса обязательно в языке иметь? Можно подобное умутить для строгого языка со скобочной записью вызовов функций?


Макросы или continuations.
Re[3]: Покритикуйте калькулятор на Haskell
От: lomeo Россия http://lomeo.livejournal.com/
Дата: 20.11.07 14:31
Оценка: 11 (2)
Здравствуйте, Кодёнок, Вы писали:

Кё>А если туда еще обработку ошибок завернуть, т.е. Result это допустим Either String Float, как это будет выглядеть?


Например,

data CalcS a = StateT Env (ErrorT String IO) a


Из-за лифтинга сможешь явно звать методы и MonadState и MonadError в одном do-блоке:

if param < 0
    then throwError "Negative" -- тут мы зовём метод MonadError
    else modify (param:)       -- тут мы зовём метод MonadState


Ссылки
http://www.haskell.org/haskellwiki/Research_papers/Monads_and_arrows#Monad_transformers
http://citeseer.ist.psu.edu/liang95monad.html (очень рекомендую начать с неё)
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[4]: Покритикуйте калькулятор на Haskell
От: lomeo Россия http://lomeo.livejournal.com/
Дата: 20.11.07 14:42
Оценка: :))
Здравствуйте, palm mute, Вы писали:

PM>Макросы или continuations.


Я когда увидел, что ты отвечаешь, сразу понял про что
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[3]: Покритикуйте калькулятор на Haskell
От: BulatZiganshin  
Дата: 20.11.07 15:30
Оценка:
Здравствуйте, Gaperton, Вы писали:

L>>
L>>x <- foo z
L>>return (bar x)
L>>


G>Прикольный язык ваш Хаскель, все-таки. Вот интересно — что для такого фокуса обязательно в языке иметь? Можно подобное умутить для строгого языка со скобочной записью вызовов функций?


"you could have invented monads" читал?
Люди, я люблю вас! Будьте бдительны!!!
Re[4]: Покритикуйте калькулятор на Haskell
От: EvilChild Ниоткуда  
Дата: 20.11.07 17:40
Оценка:
Здравствуйте, palm mute, Вы писали:

PM>Здравствуйте, Gaperton, Вы писали:


G>>Прикольный язык ваш Хаскель, все-таки. Вот интересно — что для такого фокуса обязательно в языке иметь? Можно подобное умутить для строгого языка со скобочной записью вызовов функций?


PM>Макросы или continuations.

А можно про continuations поподробнее?
now playing: Kaliber — Kaliber 1.2
Re[5]: Монады и delimited continuations
От: palm mute  
Дата: 21.11.07 18:28
Оценка: 224 (12)
Здравствуйте, EvilChild, Вы писали:

PM>>Макросы или continuations.

EC>А можно про continuations поподробнее?

Попробую. К сожалению, пишу в спешке, потому слишком подробно не получится.

In America, you execute a program.
In Soviet Russia, a program executes you!


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

В монаде State за кулисами протаскивается глобальное состояние.

В монаде List недетерминизм является также неявным, каждая строка
зависит от предыдущих.

Ключевой идеей для введения такой зависимости от контекста является
инверсия потока управления. Для этого у нас в распоряжении имеется 2
варианта.

Пишем в ивертированном стиле сами


При использовании библиотек комбинаторов программа собирается из
множества маленьких функций, соединяемых в одно целое при помощи
библиотечных комбинаторов. В типичном выражении вида f
`magicCombinator` g именно magicCombinator решает, когда и сколько раз
вызывать функции f и g (потому это и является примером инверсии
управления).

Примерами этого подхода являются монадный стиль, continuation-passing style,
ленивые потоки из SICP.

Используем continuations


Если выбранный язык программирования поддерживает delimited
continuations, нам не нужно разбивать сложное выражение на множество
маленьких функций, связанных комбинаторами. Мы можем прервать процесс
вычисления сложного выражения в любой момент, захватив текущий
контекст (стек вызовов, то что осталось вычислить), а затем вызвать
захваченный контекст как функцию:
(some..big..expression .. (capture_context ctx (magicCombinator ctx))
..)

Имея в распоряжении continuations, мы можем реализовать любую монаду.

Пример: монада State


;; magic happens here
(define (reflect ma)
  (shift k (bind ma k)))

;; ordinary State monad operations, copied verbatim from Haskell's

(define (return x)
  (lambda (s) (cons x s)))

(define (bind m f)
  (lambda (s)
    (let* ((as1 (m s))
           (a (car as1))
           (s1 (cdr as1)))
      ((f a) s1))))

(define (put x)
  (reflect (lambda (_) (cons (void) x))))

(define (get)
  (reflect (lambda (s) (cons s s))))

(define (run-state m s0)
  ((reset (return (m))) s0))

;; tests

(define (test0) 45)

(define (test1)
  (let ((x (get)))
    (begin
      (put (+ x 5))
      (put (+ (get) (get)))
      0)))

(run-state test0 10) ;; prints (45 . 10)
(run-state test1 10) ;; prints (0 . 30)


Ссылки


1. Идея реализации монад с помощью continuations принадлежит, если не
ошибаюсь, Olivier Danvy — к сожалению, не могу найти сейчас ссылку на
нужную статью

2. (Бесстыжая самореклама) Моя попытка объяснить, как работают
delimited continuations: shift/reset для самых маленьких.
Re[6]: Монады и delimited continuations
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.11.07 09:36
Оценка:
Здравствуйте, palm mute, Вы писали:

А нельзя все это дело объеденить, сделать примеры хотя бы на Питоне или Руби (а еще лучше если на C#) и пустить как статью? А то ведь сообщение затеряется...
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: Монады и delimited continuations
От: lomeo Россия http://lomeo.livejournal.com/
Дата: 22.11.07 11:16
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>А нельзя все это дело объеденить, сделать примеры хотя бы на Питоне или Руби (а еще лучше если на C#) и пустить как статью? А то ведь сообщение затеряется...


А на этих языках есть shift/reset примитивы?
Насчёт статьи +1.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[8]: Монады и delimited continuations
От: lomeo Россия http://lomeo.livejournal.com/
Дата: 22.11.07 11:29
Оценка:
Здравствуйте, lomeo, Вы писали:

L>А на этих языках есть shift/reset примитивы?


Хотя здесь shift/reset необязателен, можно и на руби calcc нарисовать. Остальные языки, IMHO, пролетают.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[9]: Монады и delimited continuations
От: palm mute  
Дата: 22.11.07 11:40
Оценка: 10 (1)
Здравствуйте, lomeo, Вы писали:

L>>А на этих языках есть shift/reset примитивы?

L>Хотя здесь shift/reset необязателен, можно и на руби calcc нарисовать. Остальные языки, IMHO, пролетают.

shift/reset редко когда бывает обязателен, просто это гораздо более подходящий инструмент во многих случаях. В Руби, насколько я понимаю, call/cc есть встроенный, а shift/reset можно эмулировать (http://www.chneukirchen.org/blog/archive/2005/04/shift-reset-and-streams.html). Но чтение примеров в вышеуказанном посте отбивает всякую охоту связываться с Руби; элементарный пример (each_to_stream) смотрится очень грязно по сравнению со Схемой.

Остальные языки однозначно пролетают. Питоновские yield'ы, конечно, штука забавная — они могут возвращать значения, С#, AFAIK, так не умеет. Но, конечно же, это не continuations — это конструкции, которые можно было бы выразить через continuations.
Re[7]: Монады и delimited continuations
От: palm mute  
Дата: 22.11.07 11:51
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>А нельзя все это дело объеденить, сделать примеры хотя бы на Питоне или Руби (а еще лучше если на C#) и пустить как статью? А то ведь сообщение затеряется...


Надо подумать. Все-таки статья и пост здесь или в ЖЖ — вещи разные. Переходить на другие языки проблематично как по техническим, так и по политическим причниам. Технически, в Питоне и C# попросту нет нужных операторов и эмулировать их никак нельзя. Политически — если я пишу с примерами на Схеме/Хаскеле/Окамле — читатель автоматически знаком с некоторыми вещами, которые не нужно объяснять. Можно ссылаться на SICP, упоминать монады и т.д. Не нужно придумывать практических примеров — можно просто показать, как эмулировать какую-то монаду. Не нужно мотивировать — читатель и так уже заинтересован в необычных технологиях и академических языках.
Re[6]: Монады и delimited continuations
От: palm mute  
Дата: 22.11.07 12:18
Оценка:
Здравствуйте, palm mute, Вы писали:

PM>1. Идея реализации монад с помощью continuations принадлежит, если не

PM>ошибаюсь, Olivier Danvy — к сожалению, не могу найти сейчас ссылку на
PM>нужную статью

Нашел ссылку. Таки ошибался, перепутал с товарища Andrzej Filinski с однофамильцем.
Representing Monads
Re[8]: Монады и delimited continuations
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.11.07 13:06
Оценка:
Здравствуйте, lomeo, Вы писали:

VD>>А нельзя все это дело объеденить, сделать примеры хотя бы на Питоне или Руби (а еще лучше если на C#) и пустить как статью? А то ведь сообщение затеряется...


L>А на этих языках есть shift/reset примитивы?

L>Насчёт статьи +1.

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

А так... как я понял эти shift/reset тоже не 100%-ное решение.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[8]: Монады и delimited continuations
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.11.07 13:06
Оценка:
Здравствуйте, palm mute, Вы писали:

PM>Надо подумать. Все-таки статья и пост здесь или в ЖЖ — вещи разные.


Дык, и эффект разный. А уж аудитория просто несравнимая.

PM>Переходить на другие языки проблематично как по техническим, так и по политическим причниам.


Откровенно говоря никогда не понимал зачем примешивать политику к инженерии. Ну, да ладно.
Просто твои примеры на Схеме поймут дай бог 0.1% посетителей сайта. А вырази ты их хотя бы на Питоне/Руби процент уже увеличится до 20. Ну, а пример на C# поймут уже процентов 70%.
Причем можно приводить примеры сразу на нескольких языках поясняя приемущества того или иного. За одно отрекламируешь что надо .

PM> Технически, в Питоне и C# попросту нет нужных операторов и эмулировать их никак нельзя.


А чего нехватает. Я что-то с ходу не уловил.

PM> Политически — если я пишу с примерами на Схеме/Хаскеле/Окамле — читатель автоматически знаком с некоторыми вещами, которые не нужно объяснять. Можно ссылаться на SICP, упоминать монады и т.д. Не нужно придумывать практических примеров — можно просто показать, как эмулировать какую-то монаду.


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

PM>Не нужно мотивировать — читатель и так уже заинтересован в необычных технологиях и академических языках.


Уверяю тебя, что читатель заинтересован. Вот только если он увидит примеры толко на Схеме, то многие скажут "игрушки". Не будем спорить о их правоте. Они просто это скажут. А если там будут примеры на более, скажем так, приземленных языках, то они с огромной вероятностью прочтут такую статью.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[9]: Монады и delimited continuations
От: palm mute  
Дата: 22.11.07 13:16
Оценка:
Здравствуйте, VladD2, Вы писали:

PM>> Технически, в Питоне и C# попросту нет нужных операторов и эмулировать их никак нельзя.

VD>А чего нехватает. Я что-то с ходу не уловил.

Ну как чего не хватает? Именно операторов shift/reset, на которых построено все изложение, нет.
Re[9]: Монады и delimited continuations
От: BulatZiganshin  
Дата: 22.11.07 13:56
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Уверяю тебя, что читатель заинтересован. Вот только если он увидит примеры толко на Схеме, то многие скажут "игрушки". Не будем спорить о их правоте. Они просто это скажут. А если там будут примеры на более, скажем так, приземленных языках, то они с огромной вероятностью прочтут такую статью.


я уже рассказывал, что проищоло когда один товарищ написал на C++ с лямбдами и lazy streams. товарищи его покрутили у виска рукой и переделали "по-человечески". я видел тот код — хотя там было что-то типа простой list comprehesion, читать его было совершенно невозможно
Люди, я люблю вас! Будьте бдительны!!!
Re[10]: Монады и delimited continuations
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.11.07 15:30
Оценка:
Здравствуйте, palm mute, Вы писали:

PM>Ну как чего не хватает? Именно операторов shift/reset, на которых построено все изложение, нет.


Можно в двух словах их смысл? Это не тоже самое что клонирование итератора?
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[10]: Монады и delimited continuations
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.11.07 15:30
Оценка:
Здравствуйте, BulatZiganshin, Вы писали:

BZ>я уже рассказывал, что проищоло когда один товарищ написал на C++ с лямбдами и lazy streams. товарищи его покрутили у виска рукой и переделали "по-человечески". я видел тот код — хотя там было что-то типа простой list comprehesion, читать его было совершенно невозможно


Кому?
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[9]: Монады и delimited continuations
От: lomeo Россия http://lomeo.livejournal.com/
Дата: 22.11.07 15:45
Оценка: +1
Здравствуйте, VladD2, Вы писали:

VD>Ну, для того чтобы повторить Хаскелевский Парсек их за глаза хватает. Вот.


Угу, читал эту статью. Только не понял при чём тут парсек?

VD> Сдается мне для данного примера C# за глаза должно хваоить.


Вряд ли. Ты в примере разобрался? По большому счёту там строится один большой reset с кучей shift внутри, за счёт чего мы получаем серию из этих delimited continuations. Как такое сделать на C# я не представляю, yield слишком маленький для этого.

VD>А так... как я понял эти shift/reset тоже не 100%-ное решение.


Почему?
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[11]: Монады и delimited continuations
От: BulatZiganshin  
Дата: 22.11.07 15:50
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, BulatZiganshin, Вы писали:


BZ>>я уже рассказывал, что проищоло когда один товарищ написал на C++ с лямбдами и lazy streams. товарищи его покрутили у виска рукой и переделали "по-человечески". я видел тот код — хотя там было что-то типа простой list comprehesion, читать его было совершенно невозможно


VD>Кому?


мне. никогда не видел boost::lambda in action?
Люди, я люблю вас! Будьте бдительны!!!
Re[11]: Монады и delimited continuations
От: lomeo Россия http://lomeo.livejournal.com/
Дата: 22.11.07 16:19
Оценка: 1 (1)
Здравствуйте, VladD2, Вы писали:

VD>Можно в двух словах их смысл? Это не тоже самое что клонирование итератора?


У palm_mute есть замечательная статья shift/reset для самых маленьких, часть 1.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[12]: Монады и delimited continuations
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.11.07 16:32
Оценка:
Здравствуйте, BulatZiganshin, Вы писали:

BZ>мне. никогда не видел boost::lambda in action?


А... Вот тут
Автор: vdimas
Дата: 22.11.07
в соседнем форуме товарищь считает, что небольшие приседения в больших объемах тлоько на пользу для ягодичных мышц.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[11]: Монады и delimited continuations
От: palm mute  
Дата: 22.11.07 16:46
Оценка: 10 (1)
Здравствуйте, VladD2, Вы писали:

PM>>Ну как чего не хватает? Именно операторов shift/reset, на которых построено все изложение, нет.

VD>Можно в двух словах их смысл? Это не тоже самое что клонирование итератора?

В двух словах? Это кажется мне затруднительным, я не так давно худо-бедно, в меру своих ограниченных литературных способностей, 19 кб текста написал, понятнее у меня сейчас вряд ли получится. Но я попробую еще раз.
reset — обозначает границу, до которой можно резать; shift — захватывает фрагмент call-стека до обозначенной границы.
Самое простое, что можно сделать с захваченным контекстом — это просто его вернуть вызывающему коду с помощью выражения (shift k k); получаем эффект, аналогичный точке останова (прямо как вызов int 3; аналогия принадлежит Олегу Киселеву).

Вот минимальный пример, без монад:
(define (break) (shift k k))

(define (forrest)
  (reset
   (begin
     (display "before breakpoint 1")
     (newline)

     (display (format "value returned from breakpoint 1: ~a" (break)))
     (newline)

     (display "before breakpoints 2 & 3")
     (newline)

     (display (format "result of complex expression with breaks inside: ~a"
                      (+ 10 (* (break) 2) (break))))
     (newline)
     "final value")))

(define (run-forrest-run)
  (let* ((snapshot1 (forrest))
         (snapshot2 (snapshot1 "eat this"))
         (alternative-snapshot2 (snapshot1 "now eat that"))
         (snapshot3 (snapshot2 10))
         (alternative-snapshot3 (alternative-snapshot2 20))         
         (final-value (snapshot3 20))
         (alternative-final-value (alternative-snapshot3 40)))
    (begin
      (display (format "final value is: ~a" final-value))
      (newline)
      (display (format "alternative final value is also: ~a" final-value))
      (newline))))


Если запустить run-forrest-run, получим следующий вывод:
before breakpoint 1
value returned from breakpoint 1: eat this
before breakpoints 2 & 3
value returned from breakpoint 1: now eat that
before breakpoints 2 & 3
result of complex expression with breaks inside: 50
result of complex expression with breaks inside: 90
final value is: final value
alternative final value is also: final value


Все переменные snapshotX, alternative-snapshotX отражают состояние процедуры forrest на какой-то из точек останова, причем запускать каждую из них можно неограниченное количество раз и притом с разными аргументами. C# так не умеет, т.к. iterator.MoveNext() не принимает никаких аргументов. Питон так умеет, но у него тоже есть ограничения. Главное ограничение — yield нельзя передать в качестве callback-функции. Код ниже совершенно бессмысленный:
def foo(f):
    items = getSomeItems()
    for item in items:
        f(item)
...
foo(yield)


А с настоящими continuations — это не проблема. Они позволяют вгрызаться в произвольный код. Олег Киселев демонстрировал, как произвольный парсер, полученный с помощью генератора парсеров, например, можно превратить из синхронного в асинхронный — легким движением руки мы заставляем его просить данные порциями (таким образом, можно парсить поток символов, поступающий из сокета, и читать из сокета только при необходимости).

О возможности клонирования шарповских итераторов я никогда не слышал.
Re[12]: Монады и delimited continuations
От: palm mute  
Дата: 22.11.07 16:52
Оценка:
Здравствуйте, lomeo, Вы писали:

L>У palm_mute есть замечательная статья shift/reset для самых маленьких, часть 1.


Кстати, мне интересно, неужто до конца никто не осилил? В исходном-то посте, с которого началось обсуждение, в конце, я ссылку на свой опус давал.
Re[13]: Монады и delimited continuations
От: lomeo Россия http://lomeo.livejournal.com/
Дата: 22.11.07 17:30
Оценка:
Здравствуйте, palm mute, Вы писали:

PM>Кстати, мне интересно, неужто до конца никто не осилил? В исходном-то посте, с которого началось обсуждение, в конце, я ссылку на свой опус давал.


Не знаю, после того как ты в ЖЖ про shift/reset рассказал, то что ты здесь написал я понял прекрасно. Ну, я надеюсь, что прекрасно

А по ссылкам не ходил. Не до того пока.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[13]: Монады и delimited continuations
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.11.07 10:04
Оценка:
Здравствуйте, palm mute, Вы писали:

PM>Кстати, мне интересно, неужто до конца никто не осилил? В исходном-то посте, с которого началось обсуждение, в конце, я ссылку на свой опус давал.


Скажу честно поглядев ее (скролингом) и увидив кучу кода на Лиспе я решил отложить ее чтение до лучших времен, так как воспринимаю Лисп с большим трудом.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Покритикуйте калькулятор на Haskell
От: Кодт Россия  
Дата: 23.11.07 12:00
Оценка:
Здравствуйте, lomeo, Вы писали:

L>Например,

L>data CalcS a = StateT Env (ErrorT String IO) a

L>Из-за лифтинга сможешь явно звать методы и MonadState и MonadError в одном do-блоке:
L>if param < 0
L>    then throwError "Negative" -- тут мы зовём метод MonadError
L>    else modify (param:)       -- тут мы зовём метод MonadState

А можешь пояснить, каким образом здесь throwError и modify оказались одного типа — судя по всему, ()->CalcS() ?
Или имелось в виду всё-таки liftM.throwError ?
if param < 0
    then liftM $ throwError "Negative"
    else modify (param:)
... << RSDN@Home 1.2.0 alpha rev. 655>>
Перекуём баги на фичи!
Re[5]: Покритикуйте калькулятор на Haskell
От: palm mute  
Дата: 23.11.07 12:07
Оценка: 1 (1) +1
Здравствуйте, Кодт, Вы писали:

К>А можешь пояснить, каким образом здесь throwError и modify оказались одного типа — судя по всему, ()->CalcS() ?

К>Или имелось в виду всё-таки liftM.throwError ?
К>
К>if param < 0
К>    then liftM $ throwError "Negative"
К>    else modify (param:)
К>


Библиотечные трансформеры монад знают друг о друге и лифтят автоматически. Некрасиво, но удобно.
Re[6]: Покритикуйте калькулятор на Haskell
От: palm mute  
Дата: 23.11.07 12:23
Оценка: 27 (1)
Здравствуйте, palm mute, Вы писали:

PM>Здравствуйте, Кодт, Вы писали:


К>>А можешь пояснить, каким образом здесь throwError и modify оказались одного типа — судя по всему, ()->CalcS() ?

К>>Или имелось в виду всё-таки liftM.throwError ?
К>>
К>>if param < 0
К>>    then liftM $ throwError "Negative"
К>>    else modify (param:)
К>>


PM>Библиотечные трансформеры монад знают друг о друге и лифтят автоматически. Некрасиво, но удобно.


Вот пример, как это выглядит, именно для класса MonadError:
instance (MonadError e m) => MonadError e (StateT s m) where
    throwError       = lift . throwError
    m `catchError` h = StateT $ \s -> runStateT m s
        `catchError` \e -> runStateT (h e) s


И, кстати, твой код неверный — там должен быть lift, liftM — это урезанная версия fmap для монад.
Re[12]: Монады и delimited continuations
От: Tonal- Россия www.promsoft.ru
Дата: 25.11.07 12:58
Оценка: 13 (2)
Здравствуйте, palm mute, Вы писали:
PM>Все переменные snapshotX, alternative-snapshotX отражают состояние процедуры forrest на какой-то из точек останова, причем запускать каждую из них можно неограниченное количество раз и притом с разными аргументами. C# так не умеет, т.к. iterator.MoveNext() не принимает никаких аргументов. Питон так умеет, но у него тоже есть ограничения. Главное ограничение — yield нельзя передать в качестве callback-функции. Код ниже совершенно бессмысленный:
PM>
PM>def foo(f):
PM>    items = getSomeItems()
PM>    for item in items:
PM>        f(item)
PM>...
PM>foo(yield)
PM>

PM>А с настоящими continuations — это не проблема. Они позволяют вгрызаться в произвольный код.
Помедитировал тут на досуге над эти, и накатал такой вот классик:
class shift(object):
  def __init__(self, func):
    def wrapper(*args, **kw):
      gen = func(*args, **kw)
      gen.next()
      return gen
    self.cons = wrapper()

  def __call__(self, item):
    self.cons.send(item)

Теперь сипользование:
def for_each(it, func):
  "Итеративная функция"
  for item in it:
    func(item)

def test():
  "Наши вычисления над итемами"
  while True:
    item = (yield)
    print item

#Запускаем "продолжения"
for_each(xrange(10), shift(test))

Можно попробывать ещё и (yield) завернуть в итератор...
Оно конечно не настоящие "продолжения" но вроде где-то рядом.

PM>О возможности клонирования шарповских итераторов я никогда не слышал.

В python-е это itertools.tee
... << RSDN@Home 1.2.0 alpha rev. 784>>
Re[13]: Монады и delimited continuations
От: palm mute  
Дата: 25.11.07 13:29
Оценка:
Здравствуйте, Tonal-, Вы писали:

PM>>А с настоящими continuations — это не проблема. Они позволяют вгрызаться в произвольный код.

T>Помедитировал тут на досуге над эти, и накатал такой вот классик: [skip]
T>def test():
T>  "Наши вычисления над итемами"
T>  while True:
T>    item = (yield)
T>    print item
T>

T>Оно конечно не настоящие "продолжения" но вроде где-то рядом.
Это не то. У тебя test — сопрограмма (coroutine, не путать с копрограммой), т.к. добровольно делает паузы с помощью оператора yield. Я задачу формулировал по-другому — превратить произвольную функцию в сопрограмму, передав ей соответствующий callback. Твоя функция for_each, как и моя foo, будучи запущенной, выполняется до конца.

PM>>О возможности клонирования шарповских итераторов я никогда не слышал.

T>В python-е это itertools.tee
За это спасибо.
Re[14]: Монады и delimited continuations
От: Tonal- Россия www.promsoft.ru
Дата: 25.11.07 15:35
Оценка:
Здравствуйте, palm mute, Вы писали:
PM>Это не то. У тебя test — сопрограмма (coroutine, не путать с копрограммой), т.к. добровольно делает паузы с помощью оператора yield. Я задачу формулировал по-другому — превратить произвольную функцию в сопрограмму, передав ей соответствующий callback. Твоя функция for_each, как и моя foo, будучи запущенной, выполняется до конца.
Вот я и пытаюсь понять можно ли обернуть конструкцию
...
  while True:
    item = (yield)
...
[py]
в итератор.
Тогда бы test мог выглядеть совершенно невинно:
[py]
def test(it):
  for item in it:
    print item

Только пока никак не могу сообразить как это сделать.

Кстати вот наткнулся на интересные ссылки:
Continuations and Stackless Python, Stackless Python
... << RSDN@Home 1.2.0 alpha rev. 784>>
Re[15]: Монады и delimited continuations
От: palm mute  
Дата: 25.11.07 16:15
Оценка:
Здравствуйте, Tonal-, Вы писали:

T>Вот я и пытаюсь понять можно ли обернуть конструкцию

T>[py]
T>...
T> while True:
T> item = (yield)
T>...
T>[py]
...
T>Только пока никак не могу сообразить как это сделать.
Думаю, это невозможно.

T>Кстати вот наткнулся на интересные ссылки:

T>Continuations and Stackless Python, Stackless Python
Я внимательно не читал, только просмотрел. Похоже, авторы добавили поддержку call/cc в Питон.
Re[14]: Монады и delimited continuations
От: lomeo Россия http://lomeo.livejournal.com/
Дата: 27.11.07 12:45
Оценка: 31 (2)
Здравствуйте, VladD2, Вы писали:

VD>Скажу честно поглядев ее (скролингом) и увидив кучу кода на Лиспе я решил отложить ее чтение до лучших времен, так как воспринимаю Лисп с большим трудом.


Кстати, забавно. Gaperton спрашивал про язык "со скобочной записью вызовов", я так понял, противопоставляя его Haskell. Поэтому я подумал, что он говорит о foo(x,y), а palm_mute о (foo x y )



Что касается кода на Лисп, то переписать это на руби (или stackless питон) трудновато без подходящих примитивов.
Вот тут есть статья, как реализовать shift/reset на руби с помощью callcc. Это один-в-один переложение реализации shift/reset на схеме (http://mumble.net/~campbell/scheme/shift-reset.scm).

@@metacont = lambda { |x|
    raise RuntimeError, "You forgot the top-level reset..."
}

def reset(&block)
    mc = @@metacont
    callcc { |k|
        @@metacont = lambda { |v|
            @@metacont = mc
            k.call v
        }
        x = block.call
        @@metacont.call x
    }
end

def shift(&block)
    callcc { |k|
        @@metacont.call block.call(lambda { |*v| reset { k.call *v } })
    }
end


Смысл в том, что мы используем глобальную переменную для того, чтобы держать контекст от reset, таким образом при вызове shift нам известен верхний контекст и мы можем "вывернуть" код наизнанку.

Лучше всего, наверное, это показать на примере. Вот код из статьи:

def each_to_stream(collection)
  reset {
    collection.each { |v|
      shift { |k|
        lambda { |&block|
          block.call v
          k.call
        }
      }
    }
    nil
  }
end


Заменим shift/reset на callcc, бета-редуцируем, уберём кое-где всякие @@metacont и вообще сократим.
Получим таким образом (псевдокод)

def each_to_stream(collection)
        callcc { |k1|
                k3({
                        collection.each(lambda { |v|
                                callcc { |k2|
                                        {k3|k1}(lambda { |b|
                                                b(v)
                                                callcc { |k3|
                                                        k3(k2([]))
                                                }
                                        })
                                }
                        })
                        nil
                })
        }
end


Здесь foo({x;y}) — это вызов с параметром, являющимся результатом выполнения блока, а не самим блоком. Также b.call(v) заменены просто на b(v). Конструкция {k3|k1} возвращает k3, если уже он был определён (во внутреннем callcc) либо k1. Плюс нам по большому счёту не важно, что вернёт k2, он тут играет роль goto.

Итак! iter = each_to_stream [1,2,3,4,5,6] нам вернёт lambda {|b| b(v); callcc {|k3| k3(k2([]))}} с контекстом k1. При вызове iter.call {|v| p v} значение будет напечатано (b(v)), дальше k2 выходит из лямбды (k2([]), реализация collection.each выбирает следующий элемент, для него делается callcc {|k2|...} и возвращается опять та же lambda {|b| b(v); callcc {|k3| k3(k2([]))}} но уже с контекстом k3, который знает о состоянии collection.each, и следовательно, который можно скопировать. Ну и дальше то же самое — печать и возврат лямбды с контекстом k3 до тех пор пока перебор не закончится, тогда возвращается nil.

Погляди на исходный код — там это записано более явно, чем через callcc — shift там сращу возвращает нужную нам лямбду, или если перебор окончен, то nil. Поэтому я говорил о выворачивании кода наизнанку, у нас получается как будто внутренний блок (в shift) зовёт внешний внутри себя. Именно благодаря этому приёму palm_mute завернул прямые вызовы в bind монады. Вот один в один код palm_mute, переложенный на ruby (я руби знаю очень плохо, поэтому поправьте меня, если что то можно сделать проще)

#
# Вот этот код "вывернется" наружу, обернув примитивы State в bind.
#
def reflect(&m)
    shift { | k | bindM(m, k) }
end

#
# Следующие две функции - это реализация монады State.
#
def returnM(x)
    lambda { |s| [x,s] }
end

def bindM(m, f)
    lambda do |s|
        as1 = m.call s
        a  = as1[0]
        s1 = as1[1]
        f.call(a).call(s1)
    end
end

#
# Следующие две функции - простые примитивы для работы с монадой State.
#
def put(s)
    reflect { |_| [[], s] }
end

def get
    reflect { |s| [s,s] }
end

#
# Вот так мы разворачиваем состояние.
#
def run_state(s0, &m)
    (reset { returnM(m.call) }).call(s0)
end

#
# Пример использования
#
def testMe
    state = run_state(10) do
        x = get
        put(x + 5)
        put(get + get)
        0
    end

    print state[1]
end

testMe


Хотя сомневаюсь, что так понятнее, чем на Схеме, но чем на callcc должно быть понятнее, это точно
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[15]: Монады и delimited continuations
От: palm mute  
Дата: 27.11.07 13:26
Оценка:
Здравствуйте, lomeo, Вы писали:


L>Кстати, забавно. Gaperton спрашивал про язык "со скобочной записью вызовов", я так понял, противопоставляя его Haskell. Поэтому я подумал, что он говорит о foo(x,y), а palm_mute о (foo x y )

L>

Я просто проигнорировал замечание о синтаксисе, т.к. оно не относится к делу; Схему было удобно использовать для примера. Если есть continuations, не нужен специальный синтаксис — мы используем стандартный, меняя его семантику.
Re[10]: Монады и delimited continuations
От: VladD2 Российская Империя www.nemerle.org
Дата: 29.11.07 12:21
Оценка:
Здравствуйте, lomeo, Вы писали:

L>Здравствуйте, VladD2, Вы писали:


VD>>Ну, для того чтобы повторить Хаскелевский Парсек их за глаза хватает. Вот.


L>Угу, читал эту статью. Только не понял при чём тут парсек?


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

VD>> Сдается мне для данного примера C# за глаза должно хваоить.


L>Вряд ли. Ты в примере разобрался? По большому счёту там строится один большой reset с кучей shift внутри, за счёт чего мы получаем серию из этих delimited continuations. Как такое сделать на C# я не представляю, yield слишком маленький для этого.


Видимо плохо я в нем разобрался. Времени нет. Можно описание на пальцах (самой задачи) без примеров на Лиспе и т.п.?

VD>>А так... как я понял эти shift/reset тоже не 100%-ное решение.


L>Почему?


Вы же сами тут говорите, что полноценные континюэшоны вроде как круче. Или я не так вас понял?
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[11]: Монады и delimited continuations
От: lomeo Россия http://lomeo.livejournal.com/
Дата: 29.11.07 13:40
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Ну, да причем? Повторили его идею комбинаторного парсера. Вместо монад использовали итераторы и объекты.


Ну так, LINQ — это и есть монада, from..in — это <-, select — это return. Да и вообще, понятно, что парсек можно повторить на других языках, вопрос в выразительности/удобстве использования.

Только при чём тут continuations, мы же вроде о них говорили в сравнении с yield? Или это была аналогия?

VD>Видимо плохо я в нем разобрался. Времени нет. Можно описание на пальцах (самой задачи) без примеров на Лиспе и т.п.?


Конкретно эта задача показывает, что монады отражаются на continuations.
shift/reset позволяют выразить это отражение элегантным образом (на чистом call/cc reflect и runState будут выглядеть похуже).

Тут просто интересно то, как это делается — связка shift-ов это по сути связка bind-ов, а т.к. shift можно писать отдельно, то мы получаем аналог do-синтаксиса в Haskell.

VD>Вы же сами тут говорите, что полноценные континюэшоны вроде как круче. Или я не так вас понял?


Они (shift/reset) тоже полноценные, просто они чуть эффективнее, т.к. позволяют руками определять границу стека, по которой резать. Ну, и в ряде случаев, более выразительнее.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[12]: Монады и delimited continuations
От: VladD2 Российская Империя www.nemerle.org
Дата: 29.11.07 16:03
Оценка:
Здравствуйте, lomeo, Вы писали:

L>Здравствуйте, VladD2, Вы писали:


VD>>Ну, да причем? Повторили его идею комбинаторного парсера. Вместо монад использовали итераторы и объекты.


L>Ну так, LINQ — это и есть монада,


Ну, это для тех кто их там очень хочет угядеть. Монады официально (штатно) есть только в Хаскеле. В C# их слава богу нет (иначе бы этот язык тоже половина народа понять не могла бы).

L> from..in — это <-, select — это return.


from, in и select всего лишь синтаксический сахар к нбору (почти стандартному) ФВП. Точнее from..in переписывается в банальное задание имени для элементов коллкций, а select так вообще чистый Map().

L>Да и вообще, понятно, что парсек можно повторить на других языках, вопрос в выразительности/удобстве использования.


Тогда что понимать по повторить. Боюсь, что повтор на С, да и на С++ могут оказаться настолько "выразительным", что все кто это увидит будут долго и нецензурно выражаться .

L>Только при чём тут continuations, мы же вроде о них говорили в сравнении с yield? Или это была аналогия?


Дык в ном примере как раз все кишки на yield и реализованы.

L>Тут просто интересно то, как это делается — связка shift-ов это по сути связка bind-ов, а т.к. shift можно писать отдельно, то мы получаем аналог do-синтаксиса в Haskell.


Не. Это объяснение мне не понять. Да и я вообще-то просил объяснить суть примера, а не что им пытались обосновать (описать).
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[13]: Монады и delimited continuations
От: VoidEx  
Дата: 29.11.07 20:02
Оценка:
Здравствуйте, VladD2, Вы писали:


div (x : int, y : int) : int
{
  call/cc (fun (return)
    {
      when (y = 0)
        return 0;
      x / y
    }
  )
}


call/cc принимает лямбду. Лямбда принимает текущий контекст, и если происходит
"вызов" этого контекста, то управление сразу переходит к этому контексту, и все значение
calc/cc становится равным аргументов, переданных контектсу.
Т.е. в данном примере, return — это что-то типа

fun (a) {
  ...
  div (x : int, y : int) : int
  {
    a
  }
  ...
}

Когда внутри лямбды, переданной внутрь calc/cc мы пишем return 0, в контекст сразу "возвращается" 0, если return не используется, то возвращается просто результат выполнения это лямбды.

Теперь reset и shift
reset обозначает границу, докуда этот контекст захватывать, а shift захватывает позволяет
его использовать, как функцию
Например
reset (10 + (shift (foo => 1 + 2 + 3 + (foo 3))))

Т.е. в данном случае foo будет равен (10 + _) и результатом всего reset-а будет
1 + 2 + 3 + (10 + 3)

Тут, конечно, псевдно-код, но, может, понятнее стало
Re[13]: Монады и delimited continuations
От: _nn_ www.nemerleweb.com
Дата: 29.11.07 20:09
Оценка: +1
Здравствуйте, Tonal-, Вы писали:

T>
T>class shift(object):
T>  def __init__(self, func):
T>    def wrapper(*args, **kw):
T>      gen = func(*args, **kw)
T>      gen.next()
T>      return gen
T>    self.cons = wrapper()

T>  def __call__(self, item):
T>    self.cons.send(item)
T>


Какой смысл wrapper-а ?
Можно и так:
def shift(func):
    cons = func()
    cons.next()
    return cons.send()
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[13]: Монады и delimited continuations
От: lomeo Россия http://lomeo.livejournal.com/
Дата: 29.11.07 23:54
Оценка: 7 (1) +1
Здравствуйте, VladD2, Вы писали:

L>>Ну так, LINQ — это и есть монада,


VD>Ну, это для тех кто их там очень хочет угядеть.


bind есть, return есть, монадическим правилам подчиняется, значит монада, т.к. отвечает её определению.

L>> from..in — это <-, select — это return.


VD>from, in и select всего лишь синтаксический сахар к нбору (почти стандартному) ФВП. Точнее from..in переписывается в банальное задание имени для элементов коллкций, а select так вообще чистый Map().


Вот именно Ты сказал то же самое, что и я, но другими словами.

Кстати, под select я имел в виду не метод Select, а конструкцию select x, которая принимая тип элемента возвращает IEnumerable над ним. Это так, к вопросу о том, что select — это map.

VD>Тогда что понимать по повторить. Боюсь, что повтор на С, да и на С++ могут оказаться настолько "выразительным", что все кто это увидит будут долго и нецензурно выражаться .


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

VD>Дык в ном примере как раз все кишки на yield и реализованы.


Э-э-э, не заметил

L>>Тут просто интересно то, как это делается — связка shift-ов это по сути связка bind-ов, а т.к. shift можно писать отдельно, то мы получаем аналог do-синтаксиса в Haskell.


VD>Не. Это объяснение мне не понять. Да и я вообще-то просил объяснить суть примера, а не что им пытались обосновать (описать).


Уф, ну постараюсь.

Давай для начала обзовём лямбды внутри get и set именами get' и set'.
Тогда

get = reflect(get')
set = reflect(set')


Дальше

reflect(m) = shift (f => m >>= f)


(здесь я вместо bind буду для удобства писать >>=)
Нам надо вычислить

(А) runState { x = get; put(x + 5); put(get + get) }

т.к. runState(m, s0) = (reset { return m })(s0) то (А) аналогично

(B) (reset { return {
    x  = shift(f => (s => (s,s)) >>= f)
        shift(f => (_ => ((), x + 5)) >>= f)
        $1 = shift(f => (s => (s,s)) >>= f)
        $2 = shift(f => (s => (s,s)) >>= f)
        shift(f => (_ => ((), $1 + $2)) >>= f)
    }})


Вот и всё! Дальше зная, что этот код вывернется, начиная с первого shift, получаем

(C) (s => (s,s)) >>=
(x => (_ => ((), x + 5)) >>=
(_ => (s => (s,s)) >>=
($1 => (s => (s,s)) >>=
($2 => (_ => ((),$1 + $2))))))
[code]

Это ключевое место, обрати внимание как (B) преобразуется в (C).
Мы просто везде вместо f подставляем готовый контекст - как будто, мы выдернули первый shift, и вместо f подставили всё что осталось в reset-блоке, дальше в нём выдернули следующий shift и т.д. пока у нас не осталось ни одного shift-а.

Т.е

[code]
reset { q; shift(k => m(k)); shift(k => n(k)); p}

эквивалентно
m(n({q;p}))


Вооот... Дальше!
Если ты посмотришь как реализован bind, то увидишь, что (С) это функция, которая протаскивает параметр через тапл, что то вроде

s0 => let (x, _) = get'(s0)      // (s0,s0)
          (_,s1) = put'(x+5)     // ((), s0+5)
                    ($1,_) = get'(s1)      // (s0+5,s0+5)
                    ($2,_) = get'(s1)      // (s0+5,s0+5)
             in ((), $1 + $2)          // ((), (s0+5)+(s0+5))


Разумеется, передав ей 10 мы получим ((), 30).
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[9]: Монады и delimited continuations
От: SmbdRsdn  
Дата: 24.04.08 21:39
Оценка:
Здравствуйте, VladD2, Вы писали:

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


А где замечание, что в статье используется сжатие C# кода до безобразия? В статье присутствует строка в 181 символ длиной.

То есть 2+ получается после ужатия C#-ного кода до безоразия (объявления и выражения на одной строке, 150-символьные строки).


отсюда
Автор: VladD2
Дата: 21.05.07
Re[13]: Монады и delimited continuations
От: Alexey Romanov  
Дата: 25.04.08 14:02
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, lomeo, Вы писали:


L>>Здравствуйте, VladD2, Вы писали:


VD>>>Ну, да причем? Повторили его идею комбинаторного парсера. Вместо монад использовали итераторы и объекты.


L>>Ну так, LINQ — это и есть монада,


VD>Ну, это для тех кто их там очень хочет угядеть. Монады официально (штатно) есть только в Хаскеле. В C# их слава богу нет (иначе бы этот язык тоже половина народа понять не могла бы).


Есть. LINQ to Objects -- это одна конкретная монада (List в Haskell), но поскольку есть ещё Query Pattern (в силу недостаточной выразительноси системы типов), то можно свободно писать любые другие монады (см. примеры здесь). Хотя, конечно, на F# оно выглядит получше.
Но когда где-нибудь появляется очередной ???LINQ (SyncLINQ, DLINQ, Push LINQ) -- это именно новая монада.

L>> from..in — это <-, select — это return.


VD>from, in и select всего лишь синтаксический сахар к нбору (почти стандартному) ФВП. Точнее from..in переписывается в банальное задание имени для элементов коллкций, а select так вообще чистый Map().


Ну да, именно так List и работает.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.