loading...

re: The Maybe data type in JavaScript VIEW POST

FULL DISCUSSION
 

If we want to mix Maybe/Either with asynchronous API fetches, there's a functional data type that's perfect for that, Task (also known as Futures). Tasks are the functional version of Promises, and can be used to wrap Promise-based apis in order to make them functional (which for our purposes in Javascript, means: coherently interoperable with other functional data types).

Here's the simplest implementation of a javascript Task:

const Task = fork => ({fork});

Pretty crazy simple (and let's not talk about why we used the word "fork" just yet)! On the surface, we're literally just setting up a way to call a variable, and then get back an object with that variable stored in a key called "fork". But what we're reaaaaaally doing here is setting up a way to delay the execution of some function (while happening to call that operation "fork"). And here's an example operation where we're fetching a resource at the api endpont, 'some-api.com/'...

Task( ( left, right ) => { fetch('http://some-api.com/').then(right).catch(left) });

Now, we could have called "left" and "right"... "error" and "success" but if we wanted to better understand how Tasks are similar to the Either Type, let's go with this naming convention.

What we get back from the above, isn't a Promise of a result, or even itself an actual call to an api, but rather: a stored proceedure that COULD make a call to that api. And, if that's ever done, this particular construct would not return a result or throw an error directly: it'd instead return the results or an errors to two specific functions: left, and right.

What are those functions? Well, most people would think of them as callbacks. In fact, a lot of imperative programming for asynchronous code used to use some version of this idiom:

makeCall( CALL_FUNCTION, ON_SUCCESS, ON_ERROR ); where all the shouty words are functions, and the last two are "callback" functions (that is, the functions that we'd call once a result or error was returned).

In our "Tasks," Left and Right are basically just ON_ERROR and ON_SUCCESS respectively (in functional programming, the error condition, by convention, is usually specified first, in part to remind us all that the error condition is easily forgotten, but needs to be handled).

Why is this Task pattern superior to the imperative version? Well, some would argue that it's not! But if you're dipping your toes into the world of functional programming, and you've been sold on exploring the Maybe or the Either data type, you might already be sold on why, and so let's assume you're sold, and move forwards:

The Task pattern is pure: it causes no side effects UNTIL it's explictly called, if ever. And that means that, despite tackling messy, potentially error-prone asynchronous operations, Tasks can still live and operate and speak the same language as all the other types that live in a synchronous world: Lists (Arrays), Maybes, Eithers... all the rest. You can .map over a Task in exactly the same way you can .map over an List (Array) or a Maybe or an Either.

The Task pattern is also, unlike its more familiar cousin, Promises, LAZY. In practice, that means that you can reason and plan and speak about Tasks without that very act of reasoning then directly causing the potentially dangerous side-effects that a commitment to purity avoids. And this paragraph is in some ways just restating the previous paragraph: Tasks are pure. It just that, because they deal with operations that are not timeless and unary in their effects, that purity must necessarily also imply laziness.

We're leading up to this: remember that fist Task operation we described? Here it was:

const A_TASK = Task( ( left, right ) => { fetch('http://some-api.com/').then(right).catch(left) });

So, here's how you'd use it:

A_TASK.fork( logError, doSuccess )

Just define whatever logError and doSuccess do to handle api results or errors for yourself: all we care about right now is that they're functions: functions in exactly the same way as are Nothing and Just, Left and Right. They basically only exist in order to continue a functional chain (and indeed, this power of Tasks/Futures is known in computer programming as a "continuation") rolling on along, all potential side-effects carefully bottled and managed. All discrete operations named and broken down in to careful atomic parts that can be then joined up again to build up a complete program that's entirely secured from errors.

But if you feel ridiculous after all of this, all for this seemingly trivial result, don't worry! The proof is in the functional pudding. Here's a glimpse of that pudding:

egghead.io/lessons/javascript-leap...

 

Thanks Drew for your answer. Indeed your example looks interesting.

Do you happen to have any more resources online on the subject? Like maybe some more open-source code and videos? I'm afraid most of the readers out there won't be able to pay the price necessary to unlock the whole course and grab the sources out of this video.

 

I wrote a couple of Medium articles way back on this stuff. For instance, here's my piece on Maybe:
medium.com/@dtipson/getting-someth...

And this gist extends some of the Task stuff (using a constructor with a prototype is still the way to go with these, but using just straight functions is often a simpler way to explain the concepts, imo) gist.github.com/dtipson/01fba81f3b...

 

The course (called Professor Frisby Introduces Composable Functional JavaScript) is actually free, you just need to enter your email address. Yes, you have to pay for the source code, but if you follow the course from the beginning it might not even be necessary, you can code while watching.

I'd recommend watching the whole course, even without coding along, because it's awesome, really.

Brian has also published a book about functional programming in JS, and it is freely available on Gitbook.

Thanks for your answer. I'll look into that. The book looks great.

Code of Conduct Report abuse