[F#] Monadic parser
От: Димчанский Литва http://dimchansky.github.io/
Дата: 12.03.09 13:34
Оценка:
Играюсь с монадическими парсерами, в смысле пытаюсь реализовать на F# (в теме функциональногог порграммирования недавно, сильно прошу не пинать). Сделал такой набросок:
#light

type Result<'s,'r> = seq<list<'s> * 'r>
type Parser<'s,'r> = Parser of (list<'s> -> Result<'s, 'r>)

let parserFun (Parser(f)) = f

let succeed v : Parser<'s,'r> = Parser(fun i -> seq{yield (i,v)})
let fail : Parser<'s,'r> = Parser(fun _ -> Seq.empty)

let satisfy p = Parser(fun i -> match i with
                                | h::t when p h -> seq{yield (t, h)}
                                | _             -> Seq.empty)
let symbol c = satisfy (fun s -> c = s)
                                                   
let (<|>) (p1:Parser<'s,'r>) (p2:Parser<'s,'r>) : Parser<'s,'r> = 
    Parser(fun i -> Seq.append (parserFun p1 i) (parserFun p2 i))
    
let (<*>) (p1:Parser<'s,'r>) (p2:Parser<'s,'t>) : Parser<'s,'r*'t> = 
    Parser(fun i -> 
              i |> parserFun p1 
                |> Seq.map (fun (rest1, result1) -> 
                    rest1 |> parserFun p2
                          |> Seq.map(fun (rest2,result2) -> (rest2,(result1,result2))))
                |> Seq.concat)
                
let (=>>) (p:Parser<'s,'r>) (f:'r->'t) : Parser<'s,'t> =
    Parser(fun i -> i |> parserFun p                                     
                      |> Seq.map (fun (rest, result) -> (rest, f result)))

let bindP (p:Parser<'s,'r>) (f:'r->Parser<'s,'t>) : Parser<'s,'t>= 
    Parser(fun i -> i |> parserFun p 
                      |> Seq.map (fun (rest, result) -> rest |> parserFun(f result))
                      |> Seq.concat)

let rec many (p:Parser<'s,'r>) : Parser<'s,'r list> =
    Parser(fun i -> i |> parserFun p 
                      |> Seq.map (fun (rest1, result1) -> 
                            rest1 |> parserFun (many p)
                                  |> Seq.map (fun (rest2, result2) -> (rest2, result1::result2)))
                      |> Seq.concat)
    <|> succeed []
                                                             
let many1 p : Parser<'s,'r list> = (p <*> many p) =>> (fun (a,b) -> a::b)

type ParserBuilder() =
    member b.Return(v) = succeed v
    member b.Bind(p,f) = bindP p f
    member b.Zero() = fail
                                    
let parser = ParserBuilder()

Можно ли как-то переписать функцию many, чтобы рекурсия стала хвостовой, а то она отваливается на совершенно смешных размерах входных данных?

P.S.:
Параллельно пытаюсь смотреть на реализацию таких парсеров на Haskell, так смотрю там как-то все проще получается и при этом орудуют только понятием списка. Стоило мне seq результатов заменить на list, как отжирания памяти стали просто громадными. Ну я подозреваю почему это происходит — вычисления списка-результата происходят энергично на каждом этапе, потому заменил его на seq. В Haskell видимо с этим проще из-за ленивости.
Re: [F#] Monadic parser
От: thesz Россия http://thesz.livejournal.com
Дата: 12.03.09 14:07
Оценка:
Здравствуйте, Димчанский, Вы писали:

Д>Играюсь с монадическими парсерами, в смысле пытаюсь реализовать на F# (в теме функциональногог порграммирования недавно, сильно прошу не пинать). Сделал такой набросок:

Д>
Д>#light
Д>let rec many (p:Parser<'s,'r>) : Parser<'s,'r list> =
Д>    Parser(fun i -> i |> parserFun p 
Д>                      |> Seq.map (fun (rest1, result1) -> 
Д>                            rest1 |> parserFun (many p)
Д>                                  |> Seq.map (fun (rest2, result2) -> (rest2, result1::result2)))
Д>                      |> Seq.concat)
Д>    <|> succeed []
                                                             
Д>let many1 p : Parser<'s,'r list> = (p <*> many p) =>> (fun (a,b) -> a::b)

Д>


С каррингом разберись, не должны many/many1 быть такими страшными в записи.

(по некоторому чтению понял, что many/many1, которые должны быть выражены через bind/return/(mplus/|||), у тебя выражены напрямую. это неправильно.)

Д>Можно ли как-то переписать функцию many, чтобы рекурсия стала хвостовой, а то она отваливается на совершенно смешных размерах входных данных?


many p = many1 p ||| return []
many1 p = pure (:) <*> p <*> many p
-- many1 p = do { x <- p; xs <- many p; return (x:xs)}


Попробуй many переписать, как чуть выше. А то она у тебя записана в терминах самой же себя.

Д>P.S.:

Д>Параллельно пытаюсь смотреть на реализацию таких парсеров на Haskell, так смотрю там как-то все проще получается и при этом орудуют только понятием списка. Стоило мне seq результатов заменить на list, как отжирания памяти стали просто громадными. Ну я подозреваю почему это происходит — вычисления списка-результата происходят энергично на каждом этапе, потому заменил его на seq. В Haskell видимо с этим проще из-за ленивости.

Да-да.

Разбор в Хаскеле осуществляется "в глубину" по самой левой ветке за счёт ленивости. У тебя энергичные вычисления превращают его в разбор в ширину, все варианты параллельно и одновременно.
Yours truly, Serguey Zefirov (thesz NA mail TOCHKA ru)
Re[2]: [F#] Monadic parser
От: Димчанский Литва http://dimchansky.github.io/
Дата: 12.03.09 14:38
Оценка:
Здравствуйте, thesz, Вы писали:

T>С каррингом разберись, не должны many/many1 быть такими страшными в записи.

T>(по некоторому чтению понял, что many/many1, которые должны быть выражены через bind/return/(mplus/|||), у тебя выражены напрямую. это неправильно.)

T>
T>many p = many1 p ||| return []
T>many1 p = pure (:) <*> p <*> many p
T>-- many1 p = do { x <- p; xs <- many p; return (x:xs)}
T>


T>Попробуй many переписать, как чуть выше. А то она у тебя записана в терминах самой же себя.


Ну можно переписать красивее, когда уже имеем ParserBuilder:
let rec many1 p = parser { let! x = p
                           let! xs = many p
                           return (x::xs) }
and many p = (many1 p) <|> succeed []

Только от этого хвостовая рекурсия не исчезает.
Re[3]: [F#] Monadic parser
От: thesz Россия http://thesz.livejournal.com
Дата: 12.03.09 14:43
Оценка:
Д>Ну можно переписать красивее, когда уже имеем ParserBuilder:
Д>
Д>let rec many1 p = parser { let! x = p
Д>                           let! xs = many p
Д>                           return (x::xs) }
Д>and many p = (many1 p) <|> succeed []
Д>

Д>Только от этого хвостовая рекурсия не исчезает.

Не появляется.

Но её и не должно быть. Но не должно быть и излишнего потребления памяти.

Есть ещё вариант: спецкомбинатор !||, который оставляет только левую ветвь разбора, если она была успешна:
(P a) !|| (P b) = P $ \input -> case a input of -- форсируем, что нужно, и проверяем
    [] -> b input
    resuls -> results

...

many p = many p !|| return []

Может помочь. Мне помогало.
Yours truly, Serguey Zefirov (thesz NA mail TOCHKA ru)
Re[3]: [F#] Monadic parser
От: Димчанский Литва http://dimchansky.github.io/
Дата: 12.03.09 14:44
Оценка:
Здравствуйте, Димчанский, Вы писали:

Д>Только от этого хвостовая рекурсия не исчезает.


Хотел сказать не появляется.
А как Haskell это место разруливает? Сам определяет как скомпилировать это так, чтобы рекурсия была б хвостовой?
Re[4]: [F#] Monadic parser
От: thesz Россия http://thesz.livejournal.com
Дата: 12.03.09 14:50
Оценка:
Д>>Только от этого хвостовая рекурсия не исчезает.
Д>Хотел сказать не появляется.
Д>А как Haskell это место разруливает? Сам определяет как скомпилировать это так, чтобы рекурсия была б хвостовой?

Там сплошные отложенные вычисления и нет никакой хвостовой рекурсии.

Скорее всего, у тебя накапливаются отложенные вычисления: одна из веток <|> никогда не вычисляется, а входной поток, который она ждёт, продолжает висеть.
Yours truly, Serguey Zefirov (thesz NA mail TOCHKA ru)
Re[4]: [F#] Monadic parser
От: Димчанский Литва http://dimchansky.github.io/
Дата: 12.03.09 14:58
Оценка:
Здравствуйте, thesz, Вы писали:

T>Не появляется.


Ага, это и хотел сказать.

T>Но её и не должно быть. Но не должно быть и излишнего потребления памяти.


Так если провести de-sugaring написанного с использованием parser{} то думаю, что получится то же самое, что у меня было.
Так а неужели здесь нельзя сделать хвостовой рекурсии?

Т.к. простой запуск
[for i in 0..10000 -> ' '] 
    |> parserFun (many (satisfy (fun s -> s=' '))) 
    |> Seq.nth 1;;

уводит интерпретатор в stack overflow.
Re[5]: [F#] Monadic parser
От: thesz Россия http://thesz.livejournal.com
Дата: 12.03.09 15:02
Оценка:
T>>Но её и не должно быть. Но не должно быть и излишнего потребления памяти.
Д>Так если провести de-sugaring написанного с использованием parser{} то думаю, что получится то же самое, что у меня было.
Д>Так а неужели здесь нельзя сделать хвостовой рекурсии?

А смысл? Всё равно они у тебя отрабатывают и отдают наружу функции высших порядков.

Д>Т.к. простой запуск

Д>
Д>[for i in 0..10000 -> ' '] 
Д>    |> parserFun (many (satisfy (fun s -> s=' '))) 
Д>    |> Seq.nth 1;;
Д>

Д>уводит интерпретатор в stack overflow.

Прошу тебя, сделай левосторонне-перекошенный комбинатор !||, перепиши с его использованием many и сообщи о результатах.
Yours truly, Serguey Zefirov (thesz NA mail TOCHKA ru)
Re[5]: [F#] Monadic parser
От: thesz Россия http://thesz.livejournal.com
Дата: 12.03.09 15:29
Оценка:
Д>Так если провести de-sugaring написанного с использованием parser{} то думаю, что получится то же самое, что у меня было.
Д>Так а неужели здесь нельзя сделать хвостовой рекурсии?
Д>Т.к. простой запуск
Д>
Д>[for i in 0..10000 -> ' '] 
Д>    |> parserFun (many (satisfy (fun s -> s=' '))) 
Д>    |> Seq.nth 1;;
Д>

Д>уводит интерпретатор в stack overflow.

import Control.Monad

data P a = P {unP :: String -> [(a,String)]}

instance Monad P where
    return a = P $ \s -> [(a,s)]
    (P a) >>= q = P $ \s -> concat [(unP (q b)) s' | (b,s') <- a s]
    fail _ = P $ const []

item = P $ \s -> case s of
    [] -> []
    (c:cs) -> [(c,cs)]

sat p = item >>= \c -> if p c then return c else fail "miserably"

(P a) ||| (P b) = P $ \s -> a s ++ b s
(P a) !|| (P b) = P $ \s -> case a s of
    [] -> b s
    xs -> xs

many p = many1 p ||| return []
many1 p = p >>= \x -> many p >>= \ xs -> return (x:xs)

many' p = many1' p !|| return []
many1' p = p >>= \x -> many' p >>= \ xs -> return (x:xs)

many'' p = run []
    where
        run acc = do
            r <- liftM Just p !|| return Nothing
            case r of
                Just x -> run (x:acc)
                Nothing -> return $ reverse acc

test many = length $ fst $ head $ (unP (many (sat (==' ')))) (replicate 100000 ' ')
{-
*Main> test many''
100000
(4.39 secs, 142657128 bytes)
*Main> test many'
*** Exception: stack overflow
*Main> test many
*** Exception: stack overflow
-}

Приём с аккумулятором работает до сих пор.

Хотя шут его знает, что там с этим энергичным F#.
Yours truly, Serguey Zefirov (thesz NA mail TOCHKA ru)
Re[6]: [F#] Monadic parser
От: Димчанский Литва http://dimchansky.github.io/
Дата: 12.03.09 15:44
Оценка:
Здравствуйте, thesz, Вы писали:

T>Прошу тебя, сделай левосторонне-перекошенный комбинатор !||, перепиши с его использованием many и сообщи о результатах.


Я написал такой оператор (он из результата первого парсера возвращает первый элемент, если такой есть, иначе пытается возвратить первый элемент из второго парсера):
let (<!|>) (p1:Parser<'s,'r>) (p2:Parser<'s,'r>) : Parser<'s,'r> = 
    Parser(fun i -> 
            seq {use e1 = (parserFun p1 i).GetEnumerator()
                 if e1.MoveNext()
                 then yield e1.Current
                 else use e2 = (parserFun p2 i).GetEnumerator()
                      if e2.MoveNext()
                      then yield e2.Current})

код для many стал таким:
let rec many1 p = parser { let! x = p
                           let! xs = many p
                           return (x::xs) }
and many p = (many1 p) <!|> succeed []

Stack overflow остался для строк длиной 10000, хотя many вроде стал пошустрее
Я думаю, что проблема в том, что в many на стеке сохраняется результат от первого прохода парсера, затем рекурсивно вызывается many, в котором происходит то же самое и т.д., а затем, когда результат от рекурсивного вызова получен, все склеивается. Наверное надо как-то с аккумулатором тут поиграться, я просто себе еще не сломал имеративное мышление, потому прошу помощи зала.
Re[6]: [F#] Monadic parser
От: Димчанский Литва http://dimchansky.github.io/
Дата: 12.03.09 15:52
Оценка:
Здравствуйте, thesz, Вы писали:

T>{-

T>*Main> test many''
T>100000
T>(4.39 secs, 142657128 bytes)
T>*Main> test many'
T>*** Exception: stack overflow
T>*Main> test many
T>*** Exception: stack overflow
T>-}
T>[/haskell]

T>Приём с аккумулятором работает до сих пор.

T>Хотя шут его знает, что там с этим энергичным F#.

Ну т.е. у Haskell те же проблемы (stack overflow) в many, если не использовать аккумулятор?
Я думаю, что у F# с применением аккумулятора, тоже должны порешиться проблемы.
Re[7]: [F#] Monadic parser
От: thesz Россия http://thesz.livejournal.com
Дата: 12.03.09 16:06
Оценка: 2 (1)
T>>Приём с аккумулятором работает до сих пор.
T>>Хотя шут его знает, что там с этим энергичным F#.

Д>Ну т.е. у Haskell те же проблемы (stack overflow) в many, если не использовать аккумулятор?


Ага. Я думал, что они магически решаются, ан нет.

Д>Я думаю, что у F# с применением аккумулятора, тоже должны порешиться проблемы.


Судя по всему, мы нашли-таки святой грааль (с маленькой буквы).
Yours truly, Serguey Zefirov (thesz NA mail TOCHKA ru)
Re[8]: [F#] Monadic parser
От: Димчанский Литва http://dimchansky.github.io/
Дата: 12.03.09 17:23
Оценка: 1 (1)
Здравствуйте, thesz, Вы писали:

Д>>Я думаю, что у F# с применением аккумулятора, тоже должны порешиться проблемы.


T>Судя по всему, мы нашли-таки святой грааль (с маленькой буквы).


Таки поборол проблему с применением аккумулятора:
let (<!|>) (p1:Parser<'s,'r>) (p2:Parser<'s,'r>) : Parser<'s,'r> = 
    Parser(fun i -> 
            seq {use e1 = (parserFun p1 i).GetEnumerator()
                 if e1.MoveNext()
                 then yield e1.Current
                 else use e2 = (parserFun p2 i).GetEnumerator()
                      if e2.MoveNext()
                      then yield e2.Current})

let many (p:Parser<'s,'r>) : Parser<'s,'r list> =
    let rec many_aux (acc:'r list) : Parser<'s,'r list> =
        Parser(fun i -> let (rest,sres) = i |> parserFun ((p =>> (fun r -> Some(r))) <!|> succeed None) |> Seq.nth 0
                        match sres with
                        | Some(res) -> rest |> parserFun (many_aux (res::acc))
                        | None      -> rest |> parserFun (succeed (List.rev acc)))
    many_aux [] <!|> succeed []

let many1 (p:Parser<'s,'r>) : Parser<'s,'r list> =
    p <*> many p =>> (fun (a, b) -> a::b)

Теперь хоть сто тысяч символов суешь — не отваливается с переполнением стека.
Re[9]: [F#] Monadic parser
От: Димчанский Литва http://dimchansky.github.io/
Дата: 12.03.09 17:29
Оценка: 1 (1)
В теле many можно выкинуть "<!|> succeed []":
let many (p:Parser<'s,'r>) : Parser<'s,'r list> =
    let rec many_aux (acc:'r list) : Parser<'s,'r list> =
        Parser(fun i -> let (rest,sres) = i |> parserFun ((p =>> (fun r -> Some(r))) <!|> succeed None) |> Seq.nth 0
                        match sres with
                        | Some(res) -> rest |> parserFun (many_aux (res::acc))
                        | None      -> rest |> parserFun (succeed (List.rev acc)))
    many_aux [] // этот <!|> succeed [] не нужен, т.к. и так будет результат с пустым множеством, если парсер не сработает
Re: [F#] Monadic parser
От: Mirrorer  
Дата: 12.03.09 20:53
Оценка:
Здравствуйте, Димчанский, Вы писали:

Д>Играюсь с монадическими парсерами, в смысле пытаюсь реализовать на F# (в теме функциональногог порграммирования недавно, сильно прошу не пинать).

можно еще посмотреть fparsec
Re[2]: [F#] Monadic parser
От: Димчанский Литва http://dimchansky.github.io/
Дата: 13.03.09 06:18
Оценка:
Здравствуйте, Mirrorer, Вы писали:

M>Здравствуйте, Димчанский, Вы писали:


Д>>Играюсь с монадическими парсерами, в смысле пытаюсь реализовать на F# (в теме функциональногог порграммирования недавно, сильно прошу не пинать).

M>можно еще посмотреть fparsec

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