← Back to context

Comment by KPGv2

16 days ago

I'm sorry, I wasn't clear. The "technically" was meant to signal "it doesnt' matter to you, but to pedants here who get off on saying "well ACKshually": I didn't forget that, it's just not relevant :D

If you want a little more elucidation, what you need to know, unless you're aiming to be functional programming god, is that:

- a monad is a FLATMAPPABLE - all monads are also applicative functors, which i will explain last bc it's kind of a twist on MAPPABLE - all applicative functors, and thus all monads, are functors, which are MAPPABLEs - an applicative functor is essentially a mappable for functions that take more than one parameter

I think applicative functors are the hardest to grok because it's not immediately obvious why they're necessary. The type signature is strange, and it's like "why would I ever put a function inside a container??" I wrote a lot of functional code in Kotlin and TypeScript before I finally understood their utility. The effect of this was that a lot of awkward code became much cleaner.

So let's begin with functor (i.e., a mappable):

Container<Integer>

if you have a function Integer to Text, a functor allows you to convert the Integer to Text using a function called `map`. We do this with arrays all the time in Python, JavaScript, etc. It's a very familiar concept, but we don't call it "functor" in those languages.

BUT, what if you have

Container<Integer>

and the function you want to map with takes two parameters. A classic example is you want to use the Integer as the first argument of a constructor. Let's say Pair.

So if Pair's constructor is: a -> a -> (a, a), you would first map Container<Integer> with PairConstructor. Now you have Container<Integer -> (Integer, Integer)>.

To pass in the second Integer to finish constructing the tuple, you use the special property of applicative functors. This is often called "ap" (like "map" without the "m").

---

Now, I would say the ACTUAL most important thing about applicative functors is this:

Imagine if you had a list of words. You want to make an API call for each word. API calls are often modeled with the Async monad (which is also, as mentioned above, definitionally an applicative functor).

But if you mapped [Word] with CallApi, you would end up with [Async ApiResult]. This models "a list of successful and unsuccessful API calls."

But what if you wanted Async [ApiResult] instead? (One might say this is an attempt to model "all api calls successful, but if one api call fails, the whole operation is considered a failure."

This is where applicative functors shine: pulling the applicative functor out of the container and wrapping the whole container. (There's more cool stuff to learn about the nature of this "container" but that'd be for another lesson, much like how you don't learn about primitives and interfaces on the same day in an OOP class.)

Recall that constructing a list of N items would be

a -> a -> a -> ... -> a (n times) -> [a]

That looks an awful lot like one MAP followed by (n-1) APs, based on the discussion above! And that's exactly what it is.

You can map the first api call and then ap the rest, and you end up going over the entire list, getting Async [ApiResult].

Now, there are a lot of ways languages go about solving this kind of "fail if one of the operations fails rather than compile a list of all successes and failures."

But the nice thing about using Functors, Monads, etc. is that you have a bunch of functions that work on these things, and they handle a ton of code so you don't have to.

That collection of Words above? It's a list. Lists are a Traversable, and all Traversable have the following function:

traverse: (a -> Applicative b) -> Traversable a -> Applicative Traversable b

The above, the traversable is a list and applicative functor was apiCall, so your code is as simple as

traverse apiCall listOfWords

No juggling around anything. That's it. You know your result will be "list of successful results, or a failure."

---

There are many more of these "type classes," and the real power comes from not needing to write a lot of code anymore because it's baked into the properties of the various type classes. Have a type that can be mapped to an order able type? Bam, now your type is order able and you never have to write a sort function for your type. Etc.