最小纯粹适用的解析器

时间:2016-02-25 22:42:41

标签: parsing haskell monads applicative

我试图弄清楚如何构建一个纯粹适用的解析器"基于简单的parser实现。解析器在其实现中不会使用monad。我先前问了这个问题,但错误地构了它,所以我再试一次。

以下是基本类型及其FunctorApplicativeAlternative实现:

newtype Parser a = Parser { parse :: String -> [(a,String)] }

instance Functor Parser where
  fmap f (Parser cs) = Parser (\s -> [(f a, b) | (a, b) <- cs s])

instance Applicative Parser where
  pure = Parser (\s -> [(a,s)])
  (Parser cs1) <*> (Parser cs2) = Parser (\s -> [(f a, s2) | (f, s1) <- cs1 s, (a, s2) <- cs2 s1])

instance Alternative Parser where
  empty = Parser $ \s -> []
  p <|> q = Parser $ \s ->
    case parse p s of
      [] -> parse q s
      r  -> r

item函数从流中取出一个字符:

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

此时,我想实施digit。我当然可以这样做:

digit = Parser $ \s ->
  case s of
    [] -> []
    (c:cs) -> if isDigit c then [(c, cs)] else []

但我正在复制item的代码。我希望根据digit实施item

如何实现digit,使用item从流中取出一个字符,然后检查字符是否为数字而不将monadic概念引入实现?

2 个答案:

答案 0 :(得分:2)

Functors允许您对某些事物值进行操作。例如,如果您有一个列表[1,2,3],则可以更改内容。请注意,Functors不允许更改结构map无法更改列表的长度。

应用程序允许您组合结构,并且内容以某种方式汇集在一起​​。 但值不能改变影响结构

即,给定item,我们可以更改其结构,我们可以更改其内容,但内容不能更改结构。我们不能选择在某些内容上失败而不是其他内容。

如果有人知道如何更正式和可证明地说明这一点,我全神贯注(这可能与自由定理有关)。

答案 1 :(得分:2)

首先,让我们写下我们目前掌握的所有工具:

-- Data constructor
Parser :: (String -> [(a, String)]) -> Parser a

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

-- instances, replace 'f' by 'Parser'
fmap  :: Functor f     =>   (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
pure  :: Applicative f =>                 a -> f a

-- the parser at hand
item :: Parser Char

-- the parser we want to write with item
digit :: Parser Char
digit = magic item

-- ?
magic :: Parser Char -> Parser Char

真正的问题是“什么是magic”?我们可以使用的东西很多。它的类型表示fmap,但我们可以排除它。我们所能提供的只是一些函数a -> b,但没有f :: Char -> Char使fmap f表示失败。

(<*>)怎么样,这有用吗?那么,再一次,答案是否定的。我们在这里唯一能做的就是从上下文中取出(a -> b)并应用它;无论在给定Applicative的背景下意味着什么。我们可以将pure排除在外。

问题是我们需要检查Char item可能解析更改上下文。我们需要像Char -> Parser Char

这样的东西

但我们没有将Parserparse排除在外!

magic p = Parser $ \s ->
  case parse p s of -- < item will be used here
    [(c, cs)] -> if isDigit c then [(c, cs)] else []
    _         -> []

是的,我知道,这是重复的代码,但现在它正在使用item。它在检查角色之前使用item。这是我们在这里使用item的唯一方法。现在,隐含了一些序列:item必须在digit能够完成它之前取得成功。

或者,我们可以尝试这种方式:

digit' c :: Char -> Parser Char
digit' c = if isDigit c then pure c else empty

但是fmap digit' item会有类型Parser (Parser Char),它只能通过类似连接的函数折叠。这就是为什么monads are more powerful than applicative

话虽如此,如果你先使用更通用的功能,你可以绕过所有的monad要求:

satisfy :: (Char -> Bool) -> Parser Char
satisfy = Parser $ \s -> 
   case s of
     (c:cs) | p c -> [(c, cs)]
     _            -> []

然后,您可以根据item定义digitsatisfy

item  = satisfy (const True)
digit = satisfy isDigit

那样digit不必检查先前解析器的结果。