Haskell: Разбор книги Грэма Хаттона (Ch-8): Что делает `parse (fv) out` и как он это делает? - PullRequest
0 голосов
/ 25 сентября 2018

Мой вопрос касается книги Грэма Хаттона Программирование в Haskell 1st Ed .

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

Базовый анализатор, называемый item, описывается следующим образом:

type Parser a = String -> [(a, String)]

item :: Parser Char

item = \inp -> case inp of
        [] -> []
        (x:xs) -> [(x,xs)]

, который используется с do для определения другого синтаксического анализатора p (do синтаксический анализатор)

p :: Parser (Char, Char)

p = do x <- item
       item
       y <- item
       return (x,y)

соответствующее связывание определение:

(>>=) :: Parser a -> (a -> Parser b) -> Parser b

p >>= f = \inp -> case parse p inp of
                       [] -> []
                       [(v,out)] -> parse (f v) out

return определяется как:

return  :: a -> Parser a

return v = \inp -> [(v,inp)]

parse определяется как:

parse :: Parser a -> String -> [(a,String)]

parse p inp = p inp

Программа (синтаксический анализатор do) берет строку и выбирает 1-й и 3-й символы и возвращает их в кортеже с остатком строки в списке, например, "abcdef" производит[('a','c'), "def"].

Я хочу знать, как (f v) out в [(v,out)] -> parse (f v) out возвращает синтаксический анализатор, который затем применяется к out.

  • f в синтаксическом анализаторе do равно item и item с символом 'c' возвращает [('c',[])]?

  • Как это может быть парсером и как он может принять out в качестве аргумента?

Возможно, я просто не понимаю, что делает (f v).

  • Кроме того, как синтаксический анализатор do 'сбрасывает' возвращаемые значения каждый раз, чтобы воздействовать на остальную часть входной строки при повторном вызове item?

  • Что это за объект, который проходит через синтаксический анализатор do, и как он изменяется на каждом шаге и каким образом он изменяется?

Ответы [ 3 ]

0 голосов
/ 26 сентября 2018

Соответствующий совет: Не паникуйте (имеется в виду, не торопите его или не торопитесь) и Следуйте указаниям .

Прежде всего, Parser s

type Parser a = String -> [(a,String)]

являются функциями от String до списков пар значений результата типа a и оставшихся строк.Эта строка остатки будет использоваться в качестве input для следующего шага синтаксического анализа.Это главное здесь.

Вы спрашиваете, в

p >>= f = \inp -> case (parse p inp) of
                       [] -> []
                       [(v,out)] -> parse (f v) out

, как (f v) в [(v,out)] -> parse (f v) out возвращает синтаксический анализатор, который затем применяется к out?

Ответ: f ' type говорит, что это так:

(>>=) :: Parser a -> (a -> Parser b) -> Parser b    -- or, the equivalent
(>>=) :: Parser a -> (a -> Parser b) -> (String -> [(b,String)])
--       p              f                inp

У нас есть f :: a -> Parser b, так что этотолько то, что он делает: применительно к значению типа a возвращает значение типа Parser b.Или, что эквивалентно,

f    :: a       -> (String  -> [(b,String)])      -- so that
f (v :: a)      ::  String  -> [(b,String)]       -- and,
f (v :: a) (out ::  String) :: [(b,String)]  

Так что, независимо от значения, которое parse p inp производит , оно должно быть тем, что f ожидает для продолжения.Типы должны «соответствовать» :

p ::       Parser a                     --   m  a
f ::              a -> Parser b         --      a -> m  b
f <$> p :: Parser    ( Parser b )       --   m     ( m  b )
p >>= f :: Parser             b         --   m          b

или, что эквивалентно,

p ::       String -> [(a,   String)]
--         inp         v    out
f ::                   a -> String   -> [(b, String)]
--                     v    out
p >>= f :: String                    -> [(b, String)]     -- a combined Parser
--         inp                            v2  out2

Так что это также отвечает на ваш второй вопрос,

Как это может быть парсером и как он может принимать out в качестве аргумента?

Реальный вопрос в том, что это за f, что делает такое?Откуда это взялось?И это ваш четвертый вопрос.

И ответ таков: ваш пример в do -обозначении,

p :: Parser (Char, Char)

p = do x <- item
       _ <- item
       y <- item
       return (x,y)

по законам Монады эквивалентен вложенной цепочке

p = do { x <- item
       ; do { _ <- item
            ; do { y <- item
                 ; return (x,y) }}}

, который является синтаксическим сахаром для вложенной цепочки Parser bind приложений,

p :: Parser (Char, Char) -- ~ String -> [((Char,Char), String)]

p = item >>= (\ x ->              -- item :: Parser Char ~ String -> [(Char,String)] 
               item >>= (\ _ ->                      -- x :: Char
                          item >>= (\ y ->           -- y :: Char
                                     return (x,y) )))

, и это , потому что функции вложены, чтофинал return имеет доступ к и y и x там;и именно Parser bind обеспечивает вывод строки остатков для использования в качестве входных данных для следующего шага синтаксического анализа:

p = item >>= f     -- :: String -> [((Char,Char), String)]
    where    
           { f x = item >>= f2
                    where { f2 _ = item >>= f3
                                    where { f3 y = return (x,y) }}}

, т. е. (при условии, что inp - это строка длиной два или более),

parse p inp                            -- assume that `inp`'s 
  = (item >>= f) inp                   --  length is at least 2     NB.
  =
    let  [(v, left)] = item inp        -- by the def of >>=
    in                                
        (f v) left
  =
    let  [(v, left)] = item inp
    in
       let  x = v                      -- inline the definition of `f`
       in  (item >>= f2) left
  =
    let  [(v, left)] = item inp
    in let  x = v 
       in let  [(v2, left2)] = item left     -- by the def of >>=, again
          in (f2 v2) left2
  =
    ..........
  =
    let  [(x,left1)] = item inp        -- x <- item
         [(_,left2)] = item left1      -- _ <- item
         [(y,left3)] = item left2      -- y <- item
    in 
        [((x,y), left3)]
  =
    let   (x:left1)  = inp             -- inline the definition
          (_:left2)  = left1           -- of `item`
          (y:left3)  = left2
    in 
        [((x,y), left3)]
  =
    let   (x:_:y:left3) = inp
    in 
        [((x,y), left3)]

после нескольких упрощений.

И это отвечает на ваш третий вопрос.

0 голосов
/ 19 февраля 2019

У меня похожие проблемы с чтением синтаксиса, потому что это не то, к чему мы привыкли.

(>>=) :: Parser a -> (a -> Parser b) -> Parser b

p >>= f = \inp -> case parse p inp of
                       [] -> []
                       [(v,out)] -> parse (f v) out

поэтому на вопрос: я хочу знать, как (fv) out в [(v, out)] -> parse (fv) out возвращает синтаксический анализатор, который затем применяется к out.

Это происходит потому, что это сигнатура 2-го аргумента ( f ): (>> =) :: Parser a -> (a -> Parser b) -> Parser b .... f принимает a и создает Parser b .a Parser b принимает String , который является out ... (fv) out

Но выводэто не должно смешиваться с выводом функции, которую мы пишем: >> =

  • Мы выводим парсер ... (>> =) ::Parser a -> (a -> Parser b) -> Parser b .
  • Парсер, который мы выводим, предназначен для переноса и объединения первых двух аргументов

Парсер - это функция, которая принимает 1 аргумент. Это строится сразу после первого = ... т.е. путем возврата (анонимной) функции: p >> = f = \ inp -> ... so inp относится к входной строке анализатора, который мы создаем

, поэтому остается только определить, что должна делать эта построенная функция ... ПРИМЕЧАНИЕ: мы не реализуем ни один из анализаторов вводапросто соединяя их вместе ... так что функция парсера output должна :

  • применить анализатор ввода ( p ) к его входу (inp): p >> = f = \ inp -> case parse p inp из
  • принять вывод этого анализа [(v, out)] v - это результат out - это то, что осталось от ввода
  • применить функцию ввода (f равно (a -> Parser b) ) к анализируемому результату (v)
  • f создает Parser b (функция, которая принимает 1 аргумент)
  • , поэтому примените этот анализатор вывода к остаток первого парсера (out)

Для меня понимание заключается в использовании деструктуризации и осознании того, что мы создаем функцию, которая склеивает выполнение других функций вместе, просто учитывая их интерфейс

Надеюсь, что это поможет ..это помогло мне написать это: -)

0 голосов
/ 25 сентября 2018

f v создает Parser b, поскольку f является функцией типа a -> Parser b, а v является значением типа a.Итак, вы звоните parse с этим Parser b и строкой out в качестве аргументов.

F в анализаторе 'do' это элемент

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

p = do x <- item
       return x

Это приведет к:

p = item >>= \x -> return x

Таким образом, правильный операнд >>=, то есть f, это \x -> return x, а не item.

Также, как парсер 'do' 'отбрасывает возвращаемые значения каждый раз, чтобы воздействовать на остальную часть входной строки при вызове элементаснова?Какой объект работает через синтаксический анализатор do и как он изменяется, а также каждый шаг и каким образом он изменяется?

Когда вы применяете синтаксический анализатор, он возвращает кортеж, содержащийпроанализированное значение и строка, представляющая остальную часть ввода.Например, если вы посмотрите на item, вторым элементом кортежа будет xs, который является хвостом входной строки (то есть строкой, содержащей все символы входной строки, кроме первого).Эта вторая часть кортежа будет тем, что подается в качестве нового ввода в последующие парсеры (согласно [(v,out)] -> parse (f v) out), так что каждый последующий парсер будет принимать в качестве входных данных строку, которую предыдущий парсер создал как вторую часть своего выходного кортежа(это будет суффикс его ввода).


В ответ на ваши комментарии:

Когда вы пишете "p = item >>= \ x -> return x ", это эквивалент только первой строки" p = do x <- item "? </p>

Нет, это эквивалентно всему do -блоку (то есть do {x <- item; return x}).Вы не можете перевести do -блокировать построчно, как это.do { x <- foo; rest } эквивалентно foo >>= \x -> do {rest}, поэтому у вас всегда будет остаток do -блока как часть правого операнда >>=.

, но не как это уменьшитпросто сделать «доступным» доступным в качестве ввода для следующей строки.Что делает анализ, если следующая строка синтаксического анализатора 'do' - это анализатор элементов?

Давайте рассмотрим пример, в котором мы дважды вызываем item (это похоже на ваш p,но без среднего пункта).Ниже я буду использовать ===, чтобы обозначить, что выражения выше и ниже === эквивалентны.

do x <- item
   y <- item
   return (x, y)
=== -- Desugaring do
item >>= \x -> item >>= \y -> return (x, y)
=== -- Inserting the definition of >>= for outer >>=
\inp -> case parse item inp of
             [] -> []
             [(v,out)] -> parse (item >>= \y -> return (v, y)) out

Теперь давайте применим это к входу "ab":

case parse item "ab" of
     [] -> []
     [(v,out)] -> parse (item >>= \y -> return (v, y)) out
=== Insert defintiion of `parse`
case item "ab" of
     [] -> []
     [(v,out)] -> parse (item >>= \y -> return (v, y)) out
=== Insert definition of item
case ('a', "b") of
     [] -> []
     [(v,out)] -> parse (item >>= \y -> return (v, y)) out
===
parse (item >>= \y -> return ('a', y)) out

Теперь мы можем расширить второй >>= так же, как мы сделали первый раз, и в итоге получим ('a', 'b').

...