applicative functor:< *>和部分应用,它是如何工作的

时间:2017-08-04 19:38:36

标签: parsing haskell applicative

我正在阅读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 ??? 

我真的不明白这里发生了什么。 有人可以一步一步地解释现在发生的事情,直到结束。

6 个答案:

答案 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 ParseraParserf <*> 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及其在你的问题中的应用是什么?

关于这些的最佳教程:

首先,当您考虑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) 的类型为pgf (a -> b)的类型为px
  • f a gpg展开parse pg inpg是元组中的第一个。
  • 使用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
    • 然后在gf2 out="abc"并将f2传递给它:
      • 解开[('a', "bc")]获取g
      • 'a'上应用[(\y -> ('a', y), "bc")]获取结果fmap
  • 然后f3上的结果out="bc" 第一个元素并将f3传递给它:
    • 解开[('b', "c")]获取'b'
    • [(('a', 'b'), "c")]上应用此功能获取最终结果:{{1}}。

总结

  • 花点时间将这些想法转移到&#34;潜水&#34;进入你特别是,法律来源于实施。
  • 下次,设计您的数据结构以便于理解。
  • Haskell是我最喜欢的语言之一,我很快就会成为你的语言,所以请耐心等待,需要学习曲线,然后你就去了!

快乐的哈斯克尔黑客攻击!

答案 5 :(得分:0)

嗯,我对Haskell没有经验,但我尝试生成Functor类型的ApplicativeParser个实例的方法如下:

-- 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")]