More fun with applicative functors

前端 未结 5 624
我在风中等你
我在风中等你 2020-12-28 17:48

Earlier I asked about translating monadic code to use only the applicative functor instance of Parsec. Unfortunately I got several replies which answered the question I lite

5条回答
  •  情歌与酒
    2020-12-28 18:43

    The <* and *> functions are very simple: they work the same way as >>. The <* would work the same way as << except << does not exist. Basically, given a *> b, you first "do" a, then you "do" b and return the result of b. For a <* b, you still first "do" a then "do" b, but you return the result of a. (For appropriate meanings of "do", of course.)

    The <$ function is just fmap const. So a <$ b is equal to fmap (const a) b. You just throw away the result of an "action" and return a constant value instead. The Control.Monad function void, which has a type Functor f => f a -> f () could be written as () <$.

    These three functions are not fundamental to the definition of an applicative functor. (<$, in fact, works for any functor.) This, again, is just like >> for monads. I believe they're in the class to make it easier to optimize them for specific instances.

    When you use applicative functors, you do not "extract" the value from the functor. In a monad, this is what >>= does, and what foo <- ... desugars to. Instead, you pass the wrapped values into a function directly using <$> and <*>. So you could rewrite your example as:

    foo <$> parser1 <*> parser2 <*> parser3 ...
    

    If you want intermediate variables, you could just use a let statement:

    let var1 = parser1
        var2 = parser2
        var3 = parser3 in
    foo <$> var1 <*> var2 <*> var3
    

    As you correctly surmised, pure is just another name for return. So, to make the shared structure more obvious, we can rewrite this as:

    pure foo <*> parser1 <*> parser2 <*> parser3
    

    I hope this clarifies things.

    Now just a little note. People do recommend using applicative functor functions for parsing. However, you should only use them if they make more sense! For sufficiently complex things, the monad version (especially with do-notation) can actually be clearer. The reason people recommend this is that

    foo <$> parser1 <*> parser2 <*> parser3
    

    is both shorter and more readable than

    do var1 <- parser1
       var2 <- parser2
       var3 <- parser3
       return $ foo var1 var2 var3
    

    Essentially, the f <$> a <*> b <*> c is essentially like lifted function application. You can imagine the <*> being a replacement for a space (e.g. function application) in the same way that fmap is a replacement for function application. This should also give you an intuitive notion of why we use <$>--it's like a lifted version of $.

提交回复
热议问题