在上一篇文章中,一位用户为Haskell提供了一个纯应用解析器的实现(代码最初来自这里)。下面是该解析器的部分实现:
{-# LANGUAGE Rank2Types #-}
import Control.Applicative (Alternative(..))
import Data.Foldable (asum, traverse_)
类型:
newtype Parser a = Parser {run :: forall f. Alternative f => (Char -> f ()) -> f a}
这些实例:
instance Functor Parser where
fmap f (Parser cont) = Parser $ char -> f <$> cont char
instance Applicative Parser where
pure a = Parser $ char -> pure a
(Parser contf) <*> (Parser cont) = Parser $ char -> (contf char) <*> (cont char)
instance Alternative Parser where
empty = Parser $ char -> empty
(Parser cont) <|> (Parser cont') = Parser $ char -> (cont char) <|> (cont' char)
some (Parser cont) = Parser $ char -> some $ cont char
many (Parser cont) = Parser $ char -> many $ cont char
一些示例解析器:
item = Parser $ char -> asum $ map (c -> c <$ char c) ['A'..'z']
digit = Parser $ char -> asum $ map (c -> c <$ char (head $ show c)) [0..9]
string s = Parser $ char -> traverse_ char s
不幸的是,我很难理解如何使用这个解析器实现。特别是,我不明白Char -> f ()
应该/可能是什么,以及如何使用它来进行简单的解析,例如从输入字符串中额外增加一个数字。如果可能的话,我想要一个具体的例子。有人可以说一些信息吗?
在forall f. Alternative f => (Char -> f ()) -> f a
中,Char -> f ()
是提供给你的东西。如果你选择接受它,你的任务就是只用这两个位把它变成一个f a
:
Char -> f ()
函数(即解析单个字符的方法:如果下一个字符与参数匹配,则解析成功;否则不会。f
Alternative
实例
那么如何将一位数字解析为Int
呢?它必须是以下形式
digit :: Parser Int
digit = Parser $ parseChar -> _
在 _
中,我们必须使用套件创建一个f Int
parseChar :: Char -> f ()
并Alternative f
.我们知道如何解析单个'0'
字符:如果下一个字符'0'
,parseChar '0'
成功。我们可以通过 f
的 Functor
实例将其转换为 Int
的值,得出
digit0 :: Parser Int
digit0 = Parser $ parseChar -> fmap (const 0) (parseChar '0')
但是f
不仅仅是Functor
,它也是Alternative
的,所以我们可以用长篇写digit
digit :: Parser Int
digit = Parser $ parseChar -> fmap (const 0) (parseChar '0') <|>
fmap (const 1) (parseChar '1') <|>
fmap (const 2) (parseChar '2') <|>
fmap (const 3) (parseChar '3') <|>
fmap (const 4) (parseChar '4') <|>
fmap (const 5) (parseChar '5') <|>
fmap (const 6) (parseChar '6') <|>
fmap (const 7) (parseChar '7') <|>
fmap (const 8) (parseChar '8') <|>
fmap (const 9) (parseChar '9')
从这里开始,这只是行人Haskell编程的问题,以减少拐杖,达到类似
digit :: Parser Int
digit = Parser $ parseChar -> asum [fmap (const d) (parseChar c) | d <- [0..9], let [c] = show d]
我们可以通过注意到fmap (const x) f
可以写成x <$ f
来进一步简化,给出
digit :: Parser Int
digit = Parser $ parseChar -> asum [d <$ parseChar c | d <- [0..9], let [c] = show d]
> Char -> f ()
部分表示单个字符的匹配。也就是说,如果你做char 'c'
,它将在'c'
上匹配,在其他所有方面都失败。
要使用它,您可以将其转换为Parsec:
convert :: Parser a -> Parsec a
convert p = run p anyChar
p
本质上属于forall f. Alternative f => (Char -> f ()) -> f a
型,专门研究(Char -> Parsec ()) -> Parsec a
。我们传入anyChar
,它将通过使用anyChar
和任何Alternative
操作生成Parsec a
值。
基本上,Parser a
它是一个函数,为了匹配单个字符和Alternative
实例,它将产生一个Alternative
值。