Haskell:将读取函数提升到parsec解析器

时间:2010-08-25 17:48:14

标签: parsing haskell parsec

作为第4次练习的一部分here 我想使用reads类型函数,例如readHex和parsec Parser

要做到这一点,我写了一个函数:

liftReadsToParse :: Parser String -> (String -> [(a, String)]) -> Parser a
liftReadsToParse p f = p >>= \s -> if null (f s) then fail "No parse" else (return . fst . head ) (f s)

可以使用哪种,例如在GHCI中,如下所示:

*Main Numeric> parse (liftReadsToParse (many1 hexDigit) readHex) "" "a1"
Right 161

任何人都可以建议对此方法进行任何改进:

  • (f s)返回null (f s)的情况下,是否会对False一词进行记忆或评估两次?
  • 处理多个成功的解析,即当length (f s)大于一时,我不知道parsec如何处理这个问题。
  • 处理解析的其余部分,即(snd . head) (f s)

  • 2 个答案:

    答案 0 :(得分:3)

    这是个好主意。一种更自然的方法 你的ReadS解析器更适合Parsec 在类型的开头留下Parser String

    liftReadS :: ReadS a -> String -> Parser a
    liftReadS reader = maybe (unexpected "no parse") (return . fst) .
                       listToMaybe . filter (null . snd) . reader
    

    这种“组合”风格非常惯用Haskell - 一旦你 习惯它,它使函数定义更容易 阅读和理解。

    然后,您可以在简单的情况下使用liftReadS

    > parse (many1 hexDigit >>= liftReadS readHex) "" "a1"
    

    (请注意,listToMaybe位于Data.Maybe模块中。)

    在更复杂的情况下,liftReadS很容易在任何内容中使用 Parsec do阻止。

    关于你的其他一些问题:

    1. 函数reader现在只应用一次,因此没有任何“memoize”。
    2. 在大多数情况下忽略除ReadS解析器中的第一个解析之外的所有解析是常见且被接受的做法,所以你没事。

    答案 1 :(得分:0)

    要回答问题的第一部分,不会记住(f s),您必须手动执行此操作:

    liftReadsToParse p f = p >>= \s -> let fs = f s in if null fs then fail "No parse"
                                                                  else (return . fst . head ) fs
    

    但我会使用模式匹配:

    liftReadsToParse p f = p >>= \s -> case f s of
                                            []              -> fail "No parse"
                                            (answer, _) : _ -> return answer