Parsing 最小纯应用解析器

Parsing 最小纯应用解析器,parsing,haskell,monads,applicative,Parsing,Haskell,Monads,Applicative,我试图找出如何基于一个简单的实现构建一个“纯应用程序解析器”。解析器不会在其实现中使用monad。我以前问过这个问题,但是我把它框错了,所以我现在再试一次 以下是基本类型及其函子、应用程序和替代实现: newtype Parser a = Parser { parse :: String -> [(a,String)] } instance Functor Parser where fmap f (Parser cs) = Parser (\s -> [(f a, b) | (

我试图找出如何基于一个简单的实现构建一个“纯应用程序解析器”。解析器不会在其实现中使用monad。我以前问过这个问题,但是我把它框错了,所以我现在再试一次

以下是基本类型及其
函子
应用程序
替代
实现:

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
此时,我想实现
数字
。我当然可以这样做:

digit = Parser $ \s ->
  case s of
    [] -> []
    (c:cs) -> if isDigit c then [(c, cs)] else []
但我正在复制
项的代码。我想基于
实现
数字


如何实现
数字
,使用
从流中删除字符,然后检查字符是否为数字,而不将一元概念带入实现中?

函子允许对某些值进行操作。例如,如果您有一个列表
[1,2,3]
,则可以更改内容。请注意,函子不允许更改结构<代码>映射
无法更改列表的长度

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

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


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

首先,让我们写下手头上的所有工具:

-- 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
是的,我知道,这是重复的代码,但现在它使用的是
item
。它在检查字符之前使用
。这是我们在这里使用
item
的唯一方法。现在,这里隐含着某种顺序:
item
必须在
digit
完成它的工作之前成功

或者,我们也可以这样尝试:

digit' c :: Char -> Parser Char
digit' c = if isDigit c then pure c else empty
但是,
fmap digit'项
将具有类型
Parser(Parser Char)
,该类型只能使用类似连接的函数进行折叠。这就是为什么

也就是说,如果首先使用更通用的函数,您可以绕过所有monad要求:

satisfy :: (Char -> Bool) -> Parser Char
satisfy = Parser $ \s -> 
   case s of
     (c:cs) | p c -> [(c, cs)]
     _            -> []
然后,您可以根据
满足
定义
数字

item  = satisfy (const True)
digit = satisfy isDigit

这样一来,
digit
就不必检查以前解析器的结果。

我认为这似乎需要单子。您可以通过将数字泛化为任意谓词来解决此问题,例如
sat::(Char->Bool)->Parser Char
。我很确定你不是故意的,不过…:-)@谢谢。你能解释一下为什么这需要单子吗?我想学习如何识别它是否需要使用monad。对于纯应用程序解析器(实际上是可选的):
newtype解析器a=parser{run::forall f.alternative f=>(Char->fa)->fa}
(您可以使用
applicative
来完成,但是
alternative
允许选择).@PyRulez你能详细解释一下吗?我看不出这个实现会是什么样子。@chi和Ana如chi所说,对扩展的实现感兴趣。也许您可以根据您提供的类型在答案中给出一个替代实现。@但使用Applicationive是不可能的。(我确实在评论中发布了我的实现,评论中也包含了一个基于monad的实现。)@Ana如果您对此有任何疑问,请告诉我(或者直接在要点中添加评论。)@Ana这是monad实现:@Ana实际上,这是链接:
satisfy :: (Char -> Bool) -> Parser Char
satisfy = Parser $ \s -> 
   case s of
     (c:cs) | p c -> [(c, cs)]
     _            -> []
item  = satisfy (const True)
digit = satisfy isDigit