Leapfrogging types with Traversable

Brian Lonsdorf
InstructorBrian Lonsdorf
Share this video with your friends

Social Share Links

Send Tweet

We use the traversable instance on List to reimplement Promise.all() type functionality.

[00:00] Here is some inputs. We have a file system and node and a task. You might not be familiar with this featurized library. Essentially, it's just like promisify. It allows us to wrap our standard node call back functions and return as task based functions.

[00:15] This is now going to work and return as a lazy task, as if we would have manually wrap this thing. OK. We have our files here. We'd like to read each one in our array and get array of results. If I were to say files.map and we get a file name. We'll call read file on a file name with UTF8 as our including.

[00:35] Let's go ahead and log this out. [inaudible] . We will get ourselves a list of tasks. Because we have an array here that has a bunch of files and we're just mapping it to tasks. If I were to run this, here we are, we have an array of tasks.

[00:50] How do we know when all of them are finished? How do we fork each one of these? We can very well map fork each one and we want to know when we are all done. Really, what we want is to take this array of tasks and turn it into a task of an array of results.

[01:05] Essentially, we want to turn these types inside out. Or leapfrog the types so that the arrays on the inside of the task and the task is on the outside of the array. Whenever we wanted to mute two types like this, what we can do is instead of calling map we call traverse.

[01:19] Now, this task will be on the outside of the array. This array does not have a traverse function so we have to use our immutable extensions list here, which we have given a traverse function to. You will find this function in any functional language you encounter, [inaudible] Scala, [inaudible] or Haskell or whatnot.

[01:39] Here we need to give it a little bit of type up, because we are not in a typesetting and it cannot figure out what the outer type should be in the case of not ever running this function. We're going to say this function can always return as a task, and so that will be the first argument, task.of is our first argument.

[01:57] How do we put it in a task if we would have not run this function? We will see an example of that later on. Here we are. We have a traverse. Now, we can take away this [inaudible] because traverse returns us the task that we can fork.

[02:09] Cancel that error, cancel that log, and here we are, we end up with a list of results. That's terrific. One task on the outside that we forked instead of a list of tasks.

[02:20] Can we re-arrange any two types?

[02:24] Terrific question. Actually, that is a very good point. Not all types have a traverse instance, that means they can't define traverse. Things like stream for instance. However, the more important thing to know is, if you see a traverse, yes you can traverse it.

[02:36] Traverse expects you to return an applicative functor from this function here. In this case task is an applicative functor and so this all works out. If we pass something that did not have in that method which this relies on under the hood, it would blow up on us. Most types are applicative functors, thank goodness. Good question.