In review, functors are really a generalization of "map". Functors are also a typeclass, so many things can be functors. Map is really a specialized functor restricted to operating on lists.
The usual functor operator is "fmap", but there is also an infix operator (a synonym for fmap) that is written <$>. I mention this right up front, because applicatives have an infix operator <*> and I am always getting these confused.
<$> - plain old functor <*> - applicative functorYou may be interested to know that applicatives were a late addition to Haskell and they came along significantly later than Monads. You can view them as beefed up functors or watered down monads.
Just so you know, you often will have a choice of whether to deal with types as applicatives or as monads. It is up to you in many cases. Monads however allow you to use "do notation".
class Functor f => Applicative (f :: * -> *) where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f bWe see the <*> applicative operator here. We also see "pure" which is a constructor we can use to "wrap" some other type (a) into an applicative (f). Usually whatever type is acting as an applicative already has a type constructor all its own to do this, so I'm not yet clear when "pure" would actually get used.
The cool <*> operator (which you could pronounce "apply") has an interesting type. It will take 2 arguments. The first is f (a -> b) which is a function wrapped as an applicative. The second object is the type "a" wrapped as an applicative. It yields "b" wrapped as an applicative.
This is exactly the same as map or fmap, except that for those, the function was "out in the open", while here the function is wrapped as an applicative. You are ahead of the game here if you can see the analogy and/or generalization of map/fmap here.
All this talk is pretty abstract. So lets use the Maybe type as a concrete example.
value = Just (*10) <*> Just 13This yields "Just 130" as a result.
Here the first Maybe holds the function (*10) which is looking to multiply something by 10. The second Maybe holds the value 13. The applicative extracts the function, and also extracts the value 13, then applies the function to 13 then wraps it back up as a Maybe.
Don't ask me when or where this would be useful, but you have now seen an applicative in action.
We could write the above example like this and get the same result:
value = pure (*10) <*> Just 13This is what we might do if we had a value in a Maybe and we knew we wanted to multiply it by 10.
value = (*10) <$> Just 13I remember my early days of Haskell when I called some function (probably "find") that returned a Maybe value. I was at my wits end wanting to work with it, and I suspected that Haskell had machinery for doing it. Now I know.
A second example is perhaps more interesting. We already know that we can use map on lists. And we know that lists are functors, so we can use fmap (or <$>) also, which will act the same as map and maybe confuse people. Lists are applicatives, and they way <*> is implemented for lists is interesting. Consider this:
newlist = [ (*10), (*2) ] <*> [7, 9]This gives us: [70,90,14,18]
Notice that the way the apply operator is defined for lists, we get a cartesian product, all the possible combinations of functions and values. It didn't have to be defined this way, but it is.
You don't get to do this:
value = Just (*10) <*> [ 1, 2 ]While both Maybe and [] are both Applicatives (the same typeclass), the types are different and this is a no-go.
pure (+) <*> Just 3 <*> Just 9Here we could write "Just (+)" rather than using "pure". Take this from left to right (the above is left associative), we could write:
(pure (+) <*> Just 3) <*> Just 9The first apply yields "Just (+3)", then we have this to evaluate:
Just +3 <*> Just 9And we end up with Just 12.
The key idea is to apply the binary (+) operator to two wrapped values.
To be able to use it, we have to "lift" it into the Maybe realm.
We can also do it with plain old fmap (<$>) like this:
(+) <$> Just 3 <*> Just 9There are also explicit functions to do lifting, so we could write:
liftA2 (+) (Just 3) (Just 2)
Here is another example
val1 = Just "dog" val2 = Just "fish" joinem a b = a ++ " ... " ++ b value = joinem <$> val1 <*> val2 main = putStrLn $ show valueThis yields Just "dog ... fish"
Here is a 20 page paper on this whole business if you want to see what the academic types say about all this:
*> - sequence two actions, keep the second result, discard the first <* - sequence two actions, keep the first result, discard the secondHere is an example of the first of these. Suppose we are doing IO and for some reason don't want to set up "do notation". We could do this.
main = putStrLn "Hello" *> putStrLn "World"We would get each on a separate line.
Hello WorldAn example of the second is this:
parseStatement = parseInteger <* parseChar ';'This is from a hypothetical parser. We want the value from the integer, but we just want to discard the semicolon that follows.
Tom's software pages / tom@mmto.org