Principled type conversions with Natural Transformations

Brian Lonsdorf
InstructorBrian Lonsdorf
Share this video with your friends

Social Share Links

Send Tweet

We learn what a natural transformation is and see the laws it must obey. We will see how a natural transformation must uphold the law of nt(x).map(f) == nt(x.map(f)).

[00:00] What the devil is a natural transformation? Simply put, and perhaps naively put, it's just a type conversion. It's taking one functor to another. A natural transformation is actually a function that takes a functor holding some a to another functor holding that a. it's a structural change.

[00:15] We'll broaden our definition in a moment, but let's go ahead and implement one of these. Let's say I have an either and I'd like to turn it into a task. We'll take our either here and we'll simply fold it out of the either and into a rejected task if it's a left or a successful task if it's a right.

[00:32] We would use it like so. If I have the right of nightingale and I call eitherToTask on that, I will now have a task I can fork. We'll just write this out so we can see it. We have error here with the e and the success case for the results.

[00:54] Running this we see we have the results nightingale, and we have a left of this error, we'll get at an error here. [laughs] That's how there is. Anywho, we'll take a natural transformation here, and let's make another one here.

[01:07] Let's turn a box into an either. It takes our box b and this box will do the same thing. We'll just fold it out and into the either. We can write it like this, which would be the same thing, but I'd like to write it first class. Here, we are taking a box of, let's say, 100.

[01:24] If we call the boxToEither it will have a right of either, a right of 100 here. Let's go ahead and look at this. We'll comment this out so we can see. There we have. We a right of 100. Why did we choose right here? Had we chosen a left we would be violating the laws of natural transformations.

[01:42] What is that law? Let's go ahead and formulize exactly what a natural transformation is here. A natural transformation is anything nt, this function, natural transformation, that when I transform x, some functor, when I map over that with f, it must be equal to mapping f over our functor, and then naturally transforming it afterwards.

[02:01] Let's go ahead and apply this law and see if it holds here. We can say on our res here, it's called res1, and we'll say first convert it with our natural transformation and then we'll map x times 2, say. Then the other side of the equation we will first map it then transform it afterwards.

[02:22] We should get the same results either way. Let's go ahead and run this. We have to get rid of all that over there. Here we are. They are indeed equal. Had I chosen a left here what would happen? This map wouldn't run after the natural transformation. These are not equal, so it must be a right.

[02:42] Indeed, any function that satisfies this equation is a natural transformation. Let's broaden our definition just a little further, or our intuition rather. If I have a function first we're taking array of xs and we simply grab the first thing out of the x, and we'll throw it in an either with fromNullable.

[02:59] What we're doing here is transforming a list into an either. We lose the rest of the list, but that doesn't matter, because this equation is still valid. Were we to write...let's do this down here. We'll say, first on 1,2,3 and we'll map x + 1, and we'll do the same here.

[03:22] We'll have 1,2,3, map, x, x +1. Then we'll transform it with first at the end. These two shall be equal and they are. Any function that satisfies this equation is a natural transformation. Let's look at this on the board here.

[03:37] If we have some F(a) and some functor holding an a and we map our f over it, it transforms that a to a b. we're just mapping a function from the type a to some type b here all inside a functor f. Then we run a natural transformation we'll have a G(b).

[03:52] If we take the other path moving downward we'll first naturally transform our functor holding an a into the G(a) here, and then we map over that to get a G(b). We end up with the same result. This can be quite useful.