我正在阅读Graham Hutton撰写的“Haskell编程编程”一书,我有一些问题需要理解<*>
和部分应用程序如何用于解析字符串。
我知道pure (+1) <*> Just 2
产生Just 3
因为pure (+1)
生成Just (+1)
,然后生成Just (+1) <*> Just 2
生成Just (2+1)
然后Just 3
但在更复杂的情况下:
-- Define a new type containing a parser function
newtype Parser a = P (String -> [(a,String)])
-- This function apply the parser p on inp
parse :: Parser a -> String -> [(a,String)]
parse (P p) inp = p inp
-- A parser which return a tuple with the first char and the remaining string
item :: Parser Char
item = P (\inp -> case inp of
[] -> []
(x:xs) -> [(x,xs)])
-- A parser is a functor
instance Functor Parser where
fmap g p = P (\inp -> case parse p inp of
[] -> []
[(v, out)] -> [(g v, out)])
-- A parser is also an applicative functor
instance Applicative Parser where
pure v = P (\inp -> [(v, inp)])
pg <*> px = P (\inp -> case parse pg inp of
[] -> []
[(g, out)] -> parse (fmap g px) out)
所以,当我这样做时:
parse (pure (\x y -> (x,y)) <*> item <*> item) "abc"
答案是:
[(('a','b'),"c")]
但我不明白到底发生了什么。 第一:
pure (\x y -> (x,y)) => P (\inp1 -> [(\x y -> (x,y), inp1)])
我现在有一个带有一个参数的解析器。
然后:
P (\inp1 -> [(\x y -> (x,y), inp1)]) <*> item
=> P (\inp2 -> case parse (\inp1 -> [(\x y -> (x,y), inp1)]) inp2 of ???
我真的不明白这里发生了什么。 有人可以一步一步地解释现在发生的事情,直到结束。
答案 0 :(得分:9)
让我们评估pure (\x y -> (x,y)) <*> item
。一旦我们看到第一个应用<*>
,第二次应用就很容易了:
P (\inp1 -> [(\x y -> (x,y), inp1)]) <*> item
我们用它的定义替换<*>
表达式,用表达式的操作数代替定义的参数。
P (\inp2 -> case parse P (\inp1 -> [(\x y -> (x,y), inp1)]) inp2 of
[] -> []
[(g, out)] -> parse (fmap g item) out)
然后我们对fmap
表达式执行相同操作。
P (\inp2 -> case parse P (\inp1 -> [(\x y -> (x,y), inp1)]) inp2 of
[] -> []
[(g, out)] -> parse P (\inp -> case parse item inp of
[] -> []
[(v, out)] -> [(g v, out)]) out)
现在我们可以减少前两个parse
表达式(我们稍后会离开parse item out
,因为它基本上是原始的。)
P (\inp2 -> case [(\x y -> (x,y), inp2)] of
[] -> []
[(g, out)] -> case parse item out of
[] -> []
[(v, out)] -> [(g v, out)])
pure (\x y -> (x,y)) <*> item
太多了。由于您通过提升类型为a -> b -> (a, b)
的二进制函数创建了第一个解析器,因此对Parser Char
类型的解析器的单个应用程序表示类型为Parser (b -> (Char, b))
的解析器。
我们可以通过输入parse
的{{1}}函数运行此解析器。由于解析器的类型为"abc"
,因此应将其减少为Parser (b -> (Char, b))
类型的值。让我们现在评估一下这个表达。
[(b -> (Char, b), String)]
根据parse P (\inp2 -> case [(\x y -> (x,y), inp2)] of
[] -> []
[(g, out)] -> case parse item out of
[] -> []
[(v, out)] -> [(g v, out)]) "abc"
的定义,这会减少到
parse
显然,在第一种情况下,模式不匹配,但在第二种情况下它们是匹配的。我们将匹配替换为第二个表达式中的模式。
case [(\x y -> (x,y), "abc")] of
[] -> []
[(g, out)] -> case parse item out of
[] -> []
[(v, out)] -> [(g v, out)]
现在我们最终评估最后case parse item "abc" of
[] -> []
[(v, out)] -> [((\x y -> (x,y)) v, out)]
个表达式。 parse
显然从parse item "abc"
的定义缩小为[('a', "bc")]
。
item
同样,第二种模式匹配,我们做替换
case [('a', "bc")] of
[] -> []
[(v, out)] -> [((\x y -> (x,y)) v, out)]
减少到
[((\x y -> (x,y)) 'a', "bc")]
如果您应用同一个流程来评估第二个[(\y -> ('a', y), "bc")] :: [(b -> (Char, b), String)] -- the expected type
应用,并将结果放在<*>
(结果)parse
表达式中,那么您将看到该表达式确实减少到"abc"
。
答案 1 :(得分:6)
在学习这些东西时帮助我很多的是关注所涉及的价值和功能的类型。这完全是关于将函数应用于值(或者将函数应用于两个值)。
($) :: (a -> b) -> a -> b
fmap :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
因此,使用 Functor ,我们在&#34;容器/上下文&#34;中的值上应用函数。 (即Maybe,List,...),以及 Applicative 我们想要应用的功能本身就在&#34;容器/上下文&#34;中。
您要应用的函数是(,)
,要应用函数的值位于容器/上下文中(在您的情况下为Parser a
)。
使用pure
我们将函数(,)
提升到同一个&#34; context / container&#34;我们的值是(注意,我们可以使用pure
将函数提升为任何 Applicative(Maybe,List,Parser,...):
(,) :: a -> b -> (a, b)
pure (,) :: Parser (a -> b -> (a, b))
使用<*>
,我们可以将(,)
上下文中的函数Parser
应用于同样位于Parser
上下文中的值。与+1
提供的示例的一个不同之处是(,)
有两个参数。因此,我们必须使用<*>
两次:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
x :: Parser Int
y :: Parser Char
let p1 = pure (,) <*> x :: Parser (b -> (Int, b))
let v1 = (,) 1 :: b -> (Int, b)
let p2 = p1 <*> y :: Parser (Int, Char)
let v2 = v1 'a' :: (Int, Char)
我们现在已经创建了一个新的解析器(p2
),我们可以像任何其他解析器一样使用它!
。 。还有更多!
看看这个便利功能:
(<$>) :: Functor f => (a -> b) -> f a -> f b
<$>
只是fmap
但您可以用它来更精美地编写组合器:
data User = User {name :: String, year :: Int}
nameParser :: Parser String
yearParser :: Parser Int
let userParser = User <$> nameParser <*> yearParser -- :: Parser User
好的,这个答案比我预期的要长!好吧,我希望它有所帮助。也许看看Typeclassopedia,我发现在学习Haskell这是一个无穷无尽的美丽过程时非常宝贵。 。 :)
答案 2 :(得分:3)
TL; DR :当你说“[现在]有一个带有一个参数”inp1
的解析器时,你感到困惑:(\x y -> (x,y))
是输入字符串到解析器,但函数 (,)
- 只是 -- by (pure (,)):
(,) -- a function expecting two arguments
-- by the first <*> combination with (item):
(,) x -- a partially applied (,) function expecting one more argument
-- by the final <*> combination with another (item):
((,) x) y == (x,y) -- the final result, a pair of `Char`s taken off the
-- input string, first (`x`) by an `item`,
-- and the second (`y`) by another `item` parser
- 正在应用于输出value(s),通过解析输入字符串产生的。临时解析器生成的值序列为:
-- pseudocode definition of `fmap`:
parse (fmap g p) inp = case (parse p inp) of -- g :: a -> b , p :: Parser a
[] -> [] -- fmap g p :: Parser b
[(v, out)] -> [(g v, out)] -- v :: a , g v :: b
通过等式推理工作通常会更容易:
-- pseudocode definition of `pure`:
parse (pure v) inp = [(v, inp)] -- v :: a , pure v :: Parser a
(显然这假设任何解析器只能生成 0 或 1 结果,因为根本不处理更长列表的情况 - 这通常是皱眉并且有充分的理由);
pure v
(使用v
解析会产生 -- pseudocode definition of `item`:
parse (item) inp = case inp of -- inp :: ['Char']
[] -> []
(x:xs) -> [(x,xs)] -- item :: Parser 'Char'
而不消耗输入);
item
(使用Char
解析意味着从输入String
的头部开始 -- pseudocode definition of `(<*>)`:
parse (pg <*> px) inp = case (parse pg inp) of -- px :: Parser a
[] -> []
[(g, out)] -> parse (fmap g px) out -- g :: a -> b
,如果可能的话);和,
<*>
(<*>
将两个解析器与适合的结果类型组合在一起,生成一个新的组合解析器,它使用第一个解析来解析输入,然后使用第二个解析器解析剩余的字符串,结合两个结果生成新的组合解析器的结果);
现在,parse ( pure (\x y -> (x,y)) <*> item <*> item ) "abc"
= parse ( (pure (,) <*> item1) <*> item2 ) "abc" -- item_i = item
将关联到左侧,所以您要问的是
<*>
最右边的= case (parse (pure (,) <*> item1) "abc") of -- by definition of <*>
[] -> []
[(g2, out2)] -> parse (fmap g2 item2) out2
= case (parse item out2) of -- by definition of fmap
[] -> []
[(v, out)] -> [(g2 v, out)]
= case out2 of -- by definition of item
[] -> []
(y:ys) -> [(g2 y, ys)]
是最顶层的,所以我们首先展开 it ,暂时保留嵌套表达式,
parse (pure (,) <*> item1) "abc"
= case (parse (pure (\x y -> (x,y))) "abc") of -- by definition of <*>
[] -> []
[(g1, out1)] -> parse (fmap g1 item1) out1
= case (parse item out1) of ....
= case out1 of
[] -> []
(x:xs) -> [(g1 x, xs)]
= case [((,), "abc")] of -- by definition of pure
[(g1, out1)] -> case out1 of
[] -> []
(x:xs) -> [(g1 x, xs)]
= let { out1 = "abc"
; g1 = (,)
; (x:xs) = out1
}
in [(g1 x, xs)]
= [( (,) 'a', "bc")]
类似地,嵌套表达式简化为
= case [( (,) 'a', "bc")] of
[(g2, out2)] -> case out2 of
[] -> []
(y:ys) -> [(g2 y, ys)]
因此我们得到了
[( ((,) 'a') 'b', "c")]
我认为你现在可以看到为什么结果会是public String yourMethod( ...., HttpServletRequest request, RedirectAttributes redirectAttributes) {
if(shouldIRedirect()) {
redirectAttributes.addAllAttributes(request.getParameterMap());
return "redirect:/newPage.html";
}
}
。
答案 3 :(得分:2)
首先,我想强调一件事。我发现理解的关键在于注意到Parser
本身与使用parse
运行解析器之间的分离。
在运行解析器时,您将Parser
和输入字符串提供给parse
,它将为您提供可能的解析列表。我认为这可能很容易理解。
您将传递parse
一个Parser
,它可以使用胶水<*>
构建。尝试理解当您通过parse
Parser
,a
或Parser
,f <*> a <*> b
时,您将传递相同类型的内容,即相当于(String -> [(a,String)])
的东西。我认为这也很容易理解,但仍需要一段时间才能“点击”。
那就是说,我会谈谈这种涂抹胶的性质<*>
。应用程序F a
是一种产生a
类型数据的计算。你可以想到一个术语,如
... f <*> g <*> h
作为一系列返回某些数据的计算,比如a
然后b
然后c
。在Parser
的上下文中,计算涉及f
在当前字符串中查找a
,然后将字符串的其余部分传递给g
等。如果有的话计算/解析失败,那么整个术语也是如此。
有趣的是,任何应用程序都可以在开头用纯函数编写来收集所有那些发出的值,所以我们一般可以写,
pure3ArgFunction <$> f <*> g <*> h
我个人认为发射和收集的心理模型很有帮助。
所以,用那么长的序言,对实际的解释。什么
parse (pure (\x y -> (x,y)) <*> item <*> item) "abc"
做什么?好吧,parse (p::Parser (Char,Char) "abc"
将解析器(我将其重命名为p
)应用于“abc”,产生[(('a','b'),"c")]
。这是一个成功的解析,返回值为('a','b')和剩余字符串“c”。
好吧,不过这不是问题。为什么解析器以这种方式工作?从:
开始.. <*> item <*> item
item
从字符串中获取下一个字符,将其作为结果生成并传递未使用的输入。下一个item
也是如此。开头可以改写为:
fmap (\x y -> (x,y)) $ item <*> item
或
(\x y -> (x,y)) <$> item <*> item
这是我表示纯函数不对输入字符串做任何事情的方式,它只是收集结果。从这个角度来看,我认为解析器应该很容易理解。很容易。太容易了。我的意思是严肃。这并不是说这个概念太难了,但是我们看待编程的正常框架对于它来说太过陌生,一开始就没有多大意义。
答案 4 :(得分:2)
以下有些人在&#34; 一步一步&#34;指导您轻松了解计算进度以创建最终结果。所以我不在这里复制它。
我认为,你真的需要深入了解Functor和Applicative Functor。一旦你理解了这些主题,其他的将很容易就像一个二三(我的意思是大多数^^)。
那么:Functor,Applicative Functor及其在你的问题中的应用是什么?
关于这些的最佳教程:
第34章&#34;为了一个好消息而学习Haskell&#34;:http://learnyouahaskell.com/functors-applicative-functors-and-monoids。
更多视觉效果&#34;图片中的Functors,Applicatives和Monads&#34;:http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html。
首先,当您考虑Functor,Applicative Functor时,请考虑上下文中的&#34;值&#34;:值很重要,而<强>计算环境也很重要。你必须同时处理它们。
类型的定义:
-- Define a new type containing a parser function
newtype Parser a = P (String -> [(a,String)])
-- This function apply the parser p on inp
parse :: Parser a -> String -> [(a,String)]
parse (P p) inp = p inp
此处的值是类型a
的值,它是列表中元组的第一个元素。
此处的上下文是函数或最终值。您必须提供输入才能获得最终值。
Parser
是一个包含在P
数据构造函数中的函数。因此,如果您得到一个值b :: Parser Char
,并且想要将其应用于某个输入,则必须在b
中展开内部函数。这就是为什么我们有函数parse
,它解开内部函数并将其应用于输入值。
并且,如果要创建Parser
值,则必须使用P
数据构造函数包装函数。
第二,Functor:可以&#34;映射&#34;结束,由 fmap :
函数指定 fmap :: (a -> b) -> f a -> f b
我经常将函数g :: (a -> b)
称为普通函数,因为您看不到上下文包围它。因此,为了能够将g
应用于f a
,我们必须以某种方式从a
中提取f a
,以便g
可以应用于{{ 1}}单独。那&#34;某种程度上&#34;取决于具体的Functor,并且是您正在使用的上下文:
a
instance Functor Parser where
fmap g p = P (\inp -> case parse p inp of
[] -> []
[(v, out)] -> [(g v, out)])
是g
类型的函数,(a -> b)
类型为p
。
要解包f a
,要获取上下文的值,我们必须传递一些输入值:p
,然后该值是元组的第一个元素。将parse p inp
应用于该值,获取类型为g
的值。
b
的结果属于fmap
类型,因此我们必须将所有结果包装在同一个上下文中,这就是为什么我们有:f b
。
目前,您可能会怀疑自己是否可以实现fmap g p = P (\inp -> ...)
,其中结果而不是fmap
为[(g v, out)]
。答案是肯定的。您可以以任何您喜欢的方式实现[(g v, inp)]
,但重要的是成为一个合适的Functor,实现必须遵守 Functor法律。法律是我们推导这些功能的实施方式(http://mvanier.livejournal.com/4586.html)。实施必须满足至少2个Functor法则:
fmap
。fmap id = id
。 fmap (f . g) = fmap f . fmap g
通常写为中缀运算符:fmap
。当您看到这个时,请查看第二个操作数以确定您正在使用哪个Functor。
第三,Applicative Functor:您将包装函数应用于包装值以获取另一个包装值:
<$>
在你的情况下:
<*> :: f (a -> b) -> f a -> f b
instance Applicative Parser where
pure v = P (\inp -> [(v, inp)])
pg <*> px = P (\inp -> case parse pg inp of
[] -> []
[(g, out)] -> parse (fmap g px) out)
的类型为pg
,f (a -> b)
的类型为px
。f a
g
向pg
展开parse pg inp
,g
是元组中的第一个。px
展开g
并将fmap g px
应用于该值。 注意,结果函数仅适用于out
,在某些情况下"bc"
不是"abc"
。P (\inp -> ...)
。与Functor一样,Applicative Functor的实现必须遵守Applicative Functor法律(在上面的教程中)。
第四,适用于您的问题:
parse (pure (\x y -> (x,y)) <*> item <*> item) "abc"
| f1 | |f2| |f3|
f1 <*> f2
传递给它,以展开"abc"
:
f1
传递给"abc"
来解开[(g, "abc")]
,我们得到fmap
。g
上f2
out="abc"
并将f2
传递给它:
[('a', "bc")]
获取g
。'a'
上应用[(\y -> ('a', y), "bc")]
获取结果:fmap
。f3
上的结果的out="bc"
第一个元素并将f3
传递给它:
[('b', "c")]
获取'b'
。[(('a', 'b'), "c")]
上应用此功能获取最终结果:{{1}}。总结:
快乐的哈斯克尔黑客攻击!
答案 5 :(得分:0)
嗯,我对Haskell没有经验,但我尝试生成Functor
类型的Applicative
和Parser
个实例的方法如下:
-- Define a new type containing a parser function
newtype Parser a = P (String -> [(a,String)])
-- This function apply the parser p on inp
parse :: Parser a -> String -> [(a,String)]
parse (P p) inp = p inp
-- A parser which return a tuple with the first char and the remaining string
item :: Parser Char
item = P (\inp -> case inp of
[] -> []
(x:xs) -> [(x,xs)])
-- A parser is a functor
instance Functor Parser where
fmap g (P f) = P (\str -> map (\(x,y) -> (g x, y)) $ f str)
-- A parser is also an applicative functor
instance Applicative Parser where
pure v = P (\str -> [(v, str)])
(P g) <*> (P f) = P (\str -> [(g' v, s) | (g',s) <- g str, (v,_) <- f str])
(P g) <*> (P f) = P (\str -> f str >>= \(v,s1) -> g s1 >>= \(g',s2) -> [(g' v,s2)])
(在<*>
上帮助@Will Ness非常需要10倍)
...因此
*Main> parse (P (\s -> [((+3), s)]) <*> pure 2) "test"
[(5,"test")]
*Main> parse (P (\s -> [((,), s ++ " altered")]) <*> pure 2 <*> pure 4) "test"
[((2,4),"test altered")]