【问题标题】:Monadic Parser - handling string with one characterMonadic Parser - 处理一个字符的字符串
【发布时间】:2021-04-18 17:37:47
【问题描述】:

我正在阅读这篇 Monadic Parsing 文章,当时我正在尝试在 Haskell 中实现一个非常简单的字符串解析器,并更好地了解如何使用 monad。在下面你可以看到我的代码,实现了匹配单个字符或整个字符串的函数。它按预期工作,但我观察到两种我无法解释的奇怪行为。

  1. 我必须处理string 中的单个字符,否则解析器将只返回空列表。确切地说,如果我删除此行string [c] = do char c; return [c],它将不再起作用。我期待string (c:s) 能正确处理string (c:[])。这可能是什么原因?

  2. 在我看来,string 定义应该等同于string s = mapM char s,因为它会为s 中的每个字符创建一个[Parser Char] 列表并将结果收集为Parser [Char]。如果我使用基于mapM 的定义,程序将陷入无限循环并且不会打印任何内容。我在这里想念一些关于惰性评估的事情吗?

.

module Main where

newtype Parser a = Parser { apply :: String->[(a, String)] }

instance Monad Parser where
    return a = Parser $ \s -> [(a, s)]
    ma >>= k = Parser $ \s -> concat [apply (k a) s' | (a, s') <- apply ma s]

instance Applicative Parser where
    pure = return
    mf <*> ma = do { f <- mf; f <$> ma; }

instance Functor Parser where
    fmap f ma = f <$> ma

empty :: Parser a
empty = Parser $ const []

anychar :: Parser Char
anychar = Parser f where
    f [] = []
    f (c:s) = [(c, s)]

satisfy :: (Char -> Bool) -> Parser Char
satisfy prop = do
    c <- anychar
    if prop c then return c
    else empty

char :: Char -> Parser Char
char c = satisfy (== c)

string :: String -> Parser String
string [] = empty
string [c] = do char c; return [c] --- if I remove this line, all results will be []
string (c:s) = do char c; string s; return (c:s)

main = do
    let s = "12345"
    print $ apply (string "123") s
    print $ apply (string "12") s
    print $ apply (string "1") s
    print $ apply (string []) s

PS。我认为问题的标题不够有启发性,如果您有更好的想法,请提出修改建议。

【问题讨论】:

  • 关于不相关的说明:考虑为 Parser 定义一个 Alternative 实例,而不是用相同的名称 (empty) 组成您自己的不相关事物。

标签: haskell monads


【解决方案1】:
  1. 由于您使用的是string [] = empty 而不是string [] = return [],因此您不能将其用作构建列表的递归的基本情况。
  2. fmap f ma = f &lt;$&gt; ma 是错误的,因为 &lt;$&gt; 是根据 fmap 定义的。如果您想根据其他实例定义fmap,请执行fmap = liftAfmap = liftM。由于mapM 在内部使用fmap,但您原来的string 没有使用,所以这个问题在您的第一个简单测试中没有出现。

【讨论】:

  • 我应该对 linter 不那么信任了。它向我暗示&lt;$&gt; 是一种较短的写作方式,甚至不认为这是自我引用。谢谢。
【解决方案2】:
string [] = empty

意思是:“如果你需要解析一个空字符串,失败——它根本无法解析,不管输入字符串是什么”。

相比之下,

string [] = return ""

意思是:“如果你需要解析一个空字符串,成功并返回空字符串——它总是可以被解析的,不管输入字符串是什么”。

通过使用第一个等式,当您在 string (c:cs) 的情况下进行递归时,您需要在一个字符 (string [c]) 处停止,因为达到零个字符将运行 empty 并使整个解析器失败。

因此,您需要使用string [c] = return [c] 等式,或者修改基本的“空字符串”大小写以使其成功。可以说,后者会更自然。

【讨论】:

    猜你喜欢
    • 2015-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多