最小纯应用解析器

Minimal Purely Applicative Parser

我正在尝试找出如何基于简单的 parser 实现构建 "purely 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 的代码。我想基于 item.

实现 digit

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

仿函数允许您根据某些值进行操作。例如,如果您有一个列表 [1,2,3],您可以更改其内容。请注意,函子不允许更改 结构 map 无法更改列表的长度。

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

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

如果有人知道如何更正式和可证明地陈述这一点,我洗耳恭听(这可能与自由定理有关)。

首先,让我们记下目前手头所有的工具:

-- 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

手头真正的问题是"what is magic"?我们能用的东西只有这么多。它的类型表示 fmap,但我们可以排除这种可能性。我们所能提供的只是一些功能 a -> b,但是没有 f :: Char -> Char 使 fmap f 指示失败。

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

问题是我们需要检查 item 可能解析 更改上下文的 Char。我们需要像 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)]
     _            -> []

然后您可以根据 satisfy:

定义 itemdigit
item  = satisfy (const True)
digit = satisfy isDigit

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