Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

More fun with applicative functors

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 literally asked, but didn't really give me much insight. So let me try this again...

Summarising my knowledge so far, an applicative functor is something which is somewhat more restricted than a monad. In the tradition of "less is more", restricting what the code can do increases the possibilities for crazy code manipulation. Regardless, a lot of people seem to believe that using applicative instead of monad is a superior solution where it's possible.

The Applicative class is defined in Control.Applicative, whose Haddock's listing helpfully separates the class methods and utility functions with a vast swathe of class instances between them, to make it hard to quickly see everything on screen at once. But the pertinent type signatures are

pure ::    x              -> f x
<*>  :: f (x -> y) -> f x -> f y
 *>  :: f  x       -> f y -> f y
<*   :: f  x       -> f y -> f x
<$>  ::   (x -> y) -> f x -> f y
<$   ::    x       -> f y -> f x

Makes perfect sense, right?

Well, Functor already gives us fmap, which is basically <$>. I.e., given a function from x to y, we can map an f x to an f y. Applicative adds two essentially new elements. One is pure, which has roughly the same type as return (and several other operators in various category theory classes). The other is <*>, which gives us the ability to take a container of functions and a container of inputs and produce a container of outputs.

Using the operators above, we can very neatly do something such as

foo <$> abc <*> def <*> ghi

This allows us to take an N-ary function and source its arguments from N functors in a way which generalises easily to any N.


This much I already understand. There are two main things which I do not yet understand.

First, the functions *>, <* and <$. From their types, <* = const, *> = flip const, and <$ could be something similar. Presumably this does not describe what these functions actually do though. (??!)

Second, when writing a Parsec parser, each parsable entity usually ends up looking something like this:

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

Since an applicative functor does not allow us to bind intermediate results to variables in this way, I'm puzzled as to how to gather them up for the final stage. I haven't been able to wrap my mind around the idea fully enough in order to comprehend how to do this.

like image 724
MathematicalOrchid Avatar asked Mar 04 '13 19:03

MathematicalOrchid


People also ask

Are functors useful?

Functors are also important because they are a building block for applicatives and monads, which are coming in future posts.

Could you comfortably explain the difference between a monad and an applicative functor?

Functors apply a function to a wrapped value: Applicatives apply a wrapped function to a wrapped value: Monads apply a function that returns a wrapped value to a wrapped value. Monads have a function >>= (pronounced "bind") to do this.

Is maybe an applicative functor?

Maybe is also an applicative functor, but more exist. The next article will give you another example. Next: Applicative validation.

Is Option A functor?

Option isn't a functor or monad in itself. A functor is a three tuple (that satisfy the functor laws); some type ( F ), the unit function ( (A) => F[A] ), and the map function ( (F[A], A => B) => F[B] ).


1 Answers

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 $.

like image 111
Tikhon Jelvis Avatar answered Oct 19 '22 08:10

Tikhon Jelvis