我的问题是关于格雷厄姆·赫顿的书“http://www.cs.nott.ac.uk/~pszgmh/book-old.html”。
在8.4节中创建了一个解析器,我假设任何回答的人都有这本书,或者可以在上面的链接中看到幻灯片8的链接。
名为item的基本解析器被描述为:
type Parser a = String -> [(a, String)]
item :: Parser Char
item = \inp -> case inp of
[] -> []
(x:xs) -> [(x,xs)]它与do一起使用来定义另一个解析器p ( do解析器)。
p :: Parser (Char, Char)
p = do x <- item
item
y <- item
return (x,y)有关的约束定义是:
(>>=) :: Parser a -> (a -> Parser b) -> Parser b
p >>= f = \inp -> case parse p inp of
[] -> []
[(v,out)] -> parse (f v) outreturn被定义为:
return :: a -> Parser a
return v = \inp -> [(v,inp)]parse被定义为:
parse :: Parser a -> String -> [(a,String)]
parse p inp = p inp程序( do解析器)接受一个字符串,并选择第一个和第三个字符,并以元组的形式返回它们,其中字符串的其余部分在列表中,例如,"abcdef"生成[('a','c'), "def"]。
我想知道(f v) out在[(v,out)] -> parse (f v) out中如何返回一个解析器,然后应用于out。
f在do解析器中是item和item接受字符'c'返回[('c',[])]吗?out作为参数呢?也许我只是不明白(f v)是做什么的。
do时,item解析器如何“丢弃”每次返回的值,以便对输入字符串的其余部分进行操作?do解析器工作的对象是什么,在每一步是如何修改的,以什么方式改变它?发布于 2018-09-25 12:55:55
f v生成Parser b,因为f是a -> Parser b类型的函数,而v是a类型的值。然后用这个parse和字符串out作为参数调用out。
“do”解析器中的f是项。
不,不是的。让我们考虑一下解析器的一个简化版本(尽管现在有点没有意义):
p = do x <- item
return x这将有损于:
p = item >>= \x -> return x因此,>>=的右操作数,即f,是\x -> return x,而不是item。
另外,每次调用item时,“do”解析器如何“删除”返回的值,以便对输入字符串的其余部分进行操作?通过“do”解析器工作的对象是什么?它是如何改变的,每一步都是如何改变的,通过什么方法改变了它?
应用解析器时,它返回包含解析值的元组和表示输入其余部分的字符串。例如,如果您查看item,元组的第二个元素将是xs,它是输入字符串的尾部(即包含输入字符串中除第一个字符以外的所有字符的字符串)。元组的第二部分将作为后续解析器的新输入(按照[(v,out)] -> parse (f v) out)提供,这样,每个连续解析器都将前一个解析器生成的字符串作为其输出元组的第二部分(这将是其输入的后缀)作为输入。
针对你的评论:
当您编写"p = item >>= \x ->返回x“时,这是否等同于第一行"p = do <- item"?
不,它相当于整个do-block (即do {x <- item; return x})。你不能像这样逐行翻译do-blocks .do { x <- foo; rest }等同于foo >>= \x -> do {rest},因此您将始终将do-block的其余部分作为>>=的右操作数的一部分。
但这并不能简化为简单地将“out”作为下一行的输入。如果“do”解析器的下一行是项解析器,那么解析在做什么?
让我们来看一个例子,其中我们调用了两次item (这类似于您的p,但没有中间项)。在下面,我将使用===来表示===上面和下面的表达式是等价的。
do x <- item
y <- item
return (x, y)
=== -- Desugaring do
item >>= \x -> item >>= \y -> return (x, y)
=== -- Inserting the definition of >>= for outer >>=
\inp -> case parse item inp of
[] -> []
[(v,out)] -> parse (item >>= \y -> return (v, y)) out现在,让我们将其应用于输入"ab":
case parse item "ab" of
[] -> []
[(v,out)] -> parse (item >>= \y -> return (v, y)) out
=== Insert defintiion of `parse`
case item "ab" of
[] -> []
[(v,out)] -> parse (item >>= \y -> return (v, y)) out
=== Insert definition of item
case ('a', "b") of
[] -> []
[(v,out)] -> parse (item >>= \y -> return (v, y)) out
===
parse (item >>= \y -> return ('a', y)) out现在,我们可以扩展第二个>>=,就像我们最初做的那样,最终得到('a', 'b')。
发布于 2019-02-19 03:06:19
我在阅读语法时遇到了类似的问题,因为这并不是我们所习惯的。
(>>=) :: Parser a -> (a -> Parser b) -> Parser b
p >>= f = \inp -> case parse p inp of
[] -> []
[(v,out)] -> parse (f v) out因此,对于这个问题:
我想知道
(f v) out在[(v,out)] -> parse (f v) out中如何返回一个解析器,然后应用于out。
这是因为这是第二艺术( f):(>>=) :: Parser a -> (a -> Parser b) -> Parser b .)的签名。f接受一个a并生成一个Parser b。Parser b采用String,即out . (f v) out.
但是它的输出不应该与我们正在编写的函数的输出相混淆:>>=。
(>>=) :: Parser a -> (a -> Parser b) -> (>>=) :: Parser a -> (a -> Parser b) ->。Parser有包装和链接前两个arg的工作。解析器是一个需要1 arg的函数。这是在第一个=之后建造的.也就是说,通过返回一个(匿名)函数:= \inp -> ...,inp引用了我们正在构建的解析器的输入字符串
所以剩下的就是定义构造函数应该做什么..。注意:我们没有实现任何一个输入解析器,只是将它们链接在一起.所以输出解析器函数应该是
p)应用于其输入(inp):p >>= f = \inp -> case parse p inp ofv的输出为结果,out是输入的剩余部分。(a -> Parser b))是f)到解析的结果(v)Parser b (一个需要1 arg的函数)out)之后的其余输入。对我来说,理解在于使用析构,并认识到我们正在构建一个函数,它将其他函数的执行结合在一起,仅仅考虑到它们的接口。
希望能帮上忙..。它帮助我写到:-)
https://stackoverflow.com/questions/52497671
复制相似问题