Applicative Functors for multiple arguments

Brian Lonsdorf
InstructorBrian Lonsdorf

Share this video with your friends

Send Tweet
Published 6 years ago
Updated 3 years ago

Working our way backwards from solution to problem, we define an applicative functor, then use it to apply a function of multiple arguments.

[00:00] Now, we're going to take a little bit of a backwards approach in understanding applicative functors here. Let's start with the solution, and work our way back to the problem it solves. We have a box of a function here, and we'd like to apply it to a box of a value.

[00:14] Here's a box holding a function, and we want to apply it to the box holding a value, and we should end up with a box of three here. We want a box three, because the two will be passed in to this function, and we will add one to it.

[00:24] Let's go ahead and define app on our box. Load up the box JS here. What we're going to do is define app, which will take a second box, and then X here is our function, X + 1. Box two holds the value. It's actually the box with two.

[00:45] We know how to apply this to that. We can just take box two, and map X over it, because X is this function, and box two is the box holding the number two. Great. We've just flipped map around here. Let's go ahead and give this a whirl, and see if it works.

[01:02] There it is. Box three. Why would we do this? Let's go ahead and take a different situation here. What if we want to take an X and a Y, and apply X + Y? By calling this, and applying it to box two, what will we have?

[01:17] We will have a box, and we've applied it to the first function -- that takes an X, which will end up being two -- and it returns us another function. Actually, a box of this function, Y, and then we applied X with two.

[01:28] This is what we have. We've applied a function that returns another function that takes another argument, and we've applied it with box two, ending up with another box. What we would do this box with a function in it, well, we'll just apply that another box of another value.

[01:41] Because it's holding a function, we can keep applying these boxes with functions in them to boxes with values in them. Let's do exactly that. Let's go ahead and apply to a box of three here. We should end up with a box of five.

[01:50] The effect we've had here is taking two boxes, and then calling a function with both arguments. We have two boxes at once. We're unboxing both of them, and passing it into this function. Let's pull this function out, and call it add here.

[02:04] Notice it's very important that this is in the curried form. It takes one argument at a time, and that's because it's going ahead and applying each box one at a time. That's how this whole situation works. The problem with solving here is you take multiple functors, and you apply each one to a function of multi...?

[02:21] Think they'll be able to see this? Ah, well, it must have been nothing. Anyway, so far, a box of X map f is really all we've had to work with. Map only gives it one argument at a time. This is a useful tool to have.

[02:38] We call this applicative functors if it has an app method. Now there are some laws here. If I have any functor f holding an X, and I call map f, that is equal to a functor holding f applied to a functor holding X.

[02:52] Now, this is the same. We can always replace one for the other. Notice here on the right side, we have a functor holding a function, and over here, we have a functor holding a value, but the function is not being held by the functor here.

[03:05] Let's go ahead and define a helper function for doing this kind of thing. We'll call it lift A2. That is two standing for two arguments, so a lift applicative with two arguments. Now what we're doing to do is take some function, and some functor with an X, and some functor with a Y.

[03:18] We'll go ahead and say I want to put f...We wanted to put f in a function, and apply it to f(x), and apply that to f(y), but we don't know what this f is here. I guess we can use some kind of introspection and reflection, and try to figure out what functor we have.

[03:34] We know that these two are equivalent. Here, we can just replace this f holding a function applied to f(x), and just replace it with fx map f, applying this above law. Now we have a completely generic line here. We don't have to mention any functors.

[03:49] We can rewrite our add here with lift A2 add, and we'll say box of two, and box of four, and add these two together. That will be our result. Let's get rid of this other result. Sure enough, we get a box of six. It adds these two boxes together.

[04:04] You can use either form here. This is what we've done before, is add the applicative form. If you squint, you can see we've got add two and four. My furry fingers here aren't hitting the keyboard correctly. We have add applied to two and four, and here, this is basically, if you squint, the same thing, just not all in boxes.

[04:24] Down here, it's a little bit clearer, perhaps, depending on your point of view. We can define a lift A3 and a lift A4. Here's a lift A3. We'll take an f(z) here, and apply f(z), and so on. That will give us the ability to apply multiple arguments to a function in a generic way.

LiveChat
LiveChat
~ 6 years ago

I've found slightly different signature of lift over the internet here.

The most 'confusing' part is not a signature of lift itself as its only matter of currying here, but the more interesting question comes when looking at the signature of lifted function. Yours comes uncurried and the other one is curried.

I'm struggling with the concept itself and would like to debug this to the atoms (and gonna do this with both approaches), just wondering about your opinion on the matter. I guess the form of those is not really that much relevant as its the idea which matters.

OTOH I see the whole functional programming as the universal contract on the ideas between languages and in that case it matters which one is more 'correct'.

Brian Lonsdorf
Brian Lonsdorfinstructor
~ 6 years ago

Good observation for sure. I think currying should be done all or nothing. It's confusing to me without type signatures if some functions are curried and others aren't. Ramda subscribes wholesale which is intuitive. I think we should go all one or the other in a single codebase

Robert Pearce
Robert Pearce
~ 4 years ago

...and now I understand liftA2. Thanks for the great walk-through