我在阅读语法时遇到类似的问题,因为这不是我们习惯的语法。
我的问题是关于Graham Hutton的书Programming in Haskell 1st Ed。
[在第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) out
return
定义为:
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
解析器工作的对象是什么,如何在每个步骤进行更改,以及通过什么方法进行更改?
f v
产生Parser b
,因为f
是a -> Parser b
类型的函数,而v
是a
类型的值。因此,您将使用此parse
和字符串Parser b
作为参数来调用out
。
'do'解析器中的F是项目
不,不是。让我们考虑一下解析器的简化版本(尽管现在已经毫无意义):
p = do x <- item
return x
这将使糖降低到:
p = item >>= \x -> return x
因此>>=
的右操作数,即f
是\x -> return x
,而不是item
。
并且每次再次调用item时,“解析器”如何每次都“丢弃”返回的值以对其余的输入字符串进行操作?通过“ do”解析器起作用的对象是什么?如何对其进行更改,以及每个步骤以及通过什么方法对其进行更改?
当您应用解析器时,它将返回一个元组,其中包含已解析的值和代表其余输入的字符串。例如,如果您查看item
,则元组的第二个元素将是xs
,它是输入字符串的尾部(即,包含除第一个以外的所有输入字符串字符的字符串)。元组的第二部分将作为新输入馈入后续解析器(按照[(v,out)] -> parse (f v) out
),这样,每个后续解析器都将前一个解析器生成的字符串作为输出元组的第二部分作为输入(这将是its输入的后缀)。
根据您的评论:
[当您写“ p = item >> = \ x-> return x”时,是否仅相当于第一行“ p = do x
否,它等于整个do
块(即do {x <- item; return x}
)。您不能像这样逐行翻译do
块。 do { x <- foo; rest }
等效于foo >>= \x -> do {rest}
,因此,您始终将do
块的其余部分作为>>=
右操作数的一部分。
但不是减少到简单地将“输出”用作下一行的输入的方式。如果'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')
。
相关建议是,不要惊慌(意味着,不要着急;或者,慢慢来),然后,跟随类型。
首先是Parser
s
type Parser a = String -> [(a,String)]
are函数从String
到a
类型的结果值对和剩余字符串的配对列表(因为type
定义了类型同义词,而不是像data
或newtype
这样的新类型) 。
该leftovers字符串将用作next解析步骤的input。这就是这里的主要内容。
您在问,in
p >>= f = \inp -> case (parse p inp) of
[] -> []
[(v,out)] -> parse (f v) out
(f v)
中的[(v,out)] -> parse (f v) out
如何返回解析器,然后将其应用于out
?
答案是,f
的type表示这样做:
(>>=) :: Parser a -> (a -> Parser b) -> Parser b -- or, the equivalent
(>>=) :: Parser a -> (a -> Parser b) -> (String -> [(b,String)])
-- p f inp
我们有f :: a -> Parser b
,所以这就是它的作用:应用于a
类型的值,它返回Parser b
类型的值。或等效地,
f :: a -> (String -> [(b,String)]) -- so that
f (v :: a) :: String -> [(b,String)] -- and,
f (v :: a) (out :: String) :: [(b,String)]
所以parse p inp
产生]的值是多少,它必须是f
等待进行的操作。类型必须“ fit”:
p :: Parser a -- m a f :: a -> Parser b -- a -> m b f <$> p :: Parser ( Parser b ) -- m ( m b ) f =<< p :: Parser b -- m b
或等效地,
p :: String -> [(a, String)] -- inp v out f :: a -> String -> [(b, String)] -- v out p >>= f :: String -> [(b, String)] -- a combined Parser -- inp v2 out2
所以这也回答了您的第二个问题,
这怎么可能是一个解析器,如何将
out
作为参数?真正的问题是,这是哪种
f
,这件事吗?它从何而来?这是你的第四个问题。
答案是,您的示例使用do
-符号,
p :: Parser (Char, Char) p = do x <- item _ <- item y <- item return (x,y)
根据Monad法则等效于嵌套链
应用程序的嵌套链的语法糖,p = do { x <- item ; do { _ <- item ; do { y <- item ; return (x,y) }}}
这是
Parser
bind
嵌套函数,使得最终的p :: Parser (Char, Char) -- ~ String -> [((Char,Char), String)] p = item >>= (\ x -> -- item :: Parser Char ~ String -> [(Char,String)] item >>= (\ _ -> -- x :: Char item >>= (\ y -> -- y :: Char return (x,y) )))
并且是因为
return
可以访问bothy
and x
;而正是Parser
bind安排输出的剩余字符串用作下一解析步骤的输入:p = item >>= f -- :: String -> [((Char,Char), String)] where { f x = item >>= f2 where { f2 _ = item >>= f3 where { f3 y = return (x,y) }}}
即(假设
inp
是长度为2或更长的字符串),>parse p inp -- assume that `inp`'s = (item >>= f) inp -- length is at least 2 NB. = let [(v, left)] = item inp -- by the def of >>= in (f v) left = let [(v, left)] = item inp in let x = v -- inline the definition of `f` in (item >>= f2) left = let [(v, left)] = item inp in let x = v in let [(v2, left2)] = item left -- by the def of >>=, again in (f2 v2) left2 = .......... = let [(x,left1)] = item inp -- x <- item [(_,left2)] = item left1 -- _ <- item [(y,left3)] = item left2 -- y <- item in [((x,y), left3)] = let (x:left1) = inp -- inline the definition (_:left2) = left1 -- of `item` (y:left3) = left2 in [((x,y), left3)] = let (x:_:y:left3) = inp in [((x,y), left3)]
经过一些简化。
这将回答您的第三个问题。
我在阅读语法时遇到类似的问题,因为这不是我们习惯的语法。
(>>=) :: Parser a -> (a -> Parser b) -> Parser b
p >>= f = \inp -> case parse p inp of
[] -> []
[(v,out)] -> parse (f v) out
因此提出以下问题:我想知道[[v,out)]-> parse(f v)out中的(f v)如何返回解析器,然后将其应用于out。
这是因为那是第二个arg(f)的标志:(>> =)::解析器a-> ((a->解析器b)
->解析器b ... 。[[f接受a并产生Parser b。一个解析器b接受一个String,它是out ...(f v)out但是此输出不应与我们正在编写的函数的输出混合:>> =
这是在第一个=]之后构造的,即通过返回一个(匿名)函数:p >> = f = \ inp-> ...
注意:我们没有实现任何输入分析器,只是将它们链接在一起
...所以,输出分析器函数应该 :希望有帮助...它帮助我写了:-)
我在阅读语法时遇到类似的问题,因为这不是我们习惯的语法。