纯粹适用于使用Alternative的Parser

时间:2016-02-26 20:24:37

标签: parsing haskell applicative higher-rank-types

在之前的post中,用户为Haskell提供了纯粹适用的解析器的实现(代码最初来自here)。下面是该解析器的部分实现:

{-# 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 ()应该/可能是什么以及如何使用它来进行简单的解析,例如从输入字符串中增加一个数字。如果可能的话,我想要一个具体的例子。有人可以解释一下吗?

2 个答案:

答案 0 :(得分:2)

forall f. Alternative f => (Char -> f ()) -> f a中,Char -> f ()提供的内容。如果您选择接受它,那么您的任务就是使用这两个位将其转换为f a

  • Char -> f ()函数(即解析单个字符的方法:如果下一个字符与参数匹配,则解析成功;否则它不会。)
  • Alternative
  • f个实例

那你怎么把一个数字解析成Int?它必须是

的形式
digit :: Parser Int
digit = Parser $ \parseChar -> _

_中,我们必须使用工具包f IntparseChar :: Char -> f ()创建Alternative f。我们知道如何解析单个'0'字符:如果下一个字符为parseChar '0',则'0'成功。我们可以通过Int f个实例将其转换为Functor的值,到达

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]

答案 1 :(得分:-1)

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,并使用Parsec a和任何anyChar操作生成Alternative值。

基本上,Parser a它是一个函数,赠送给匹配单个字符和Alternative实例,它将产生Alternative值。