© Anto Aravinth 2017

Anto Aravinth, Beginning Functional JavaScript, 10.1007/978-1-4842-2656-8_8

8. Fun with Functors

Anto Aravinth

(1)Chennai, Tamil Nadu, India

Note

The chapter examples and library source code are in branch chap08. The repo’s URL is: https://github.com/antoaravinth/functional-es6.git

Once checkout the code, please checkout branch chap08:

...

git checkout -b chap08 origin/chap08

...

For running the codes, as before run:

...

npm run playground

...

In our previous chapter, we dealt with many functional programming techniques. In this chapter we are going to see yet important concept in programming called error handling. Error handling is a common programming technique for handling errors in your application. But the functional programming way of error handling will be different, and that’s exactly what we are going to see in the current chapter.

We will be looking at a new concept called Functor . This new friend will be introduced, and it is going to help us to handle errors in a purely functional way. Once we grasp the idea of Functor, we are going to implement two real-world functors: namely, MayBe and Either. So let’s get started.

What Is a Functor?

In this section we are going to see what a Functor really is. Here is its definition :

  • Functor is a plain object (or type class in other languages) that implements the function map that, while running over each value in the object to produce a new object.

Hmmm, that's the definition of Functor. But it is not as easy to understand the definition at first sight. We are going to break it down step by step section so that we clearly understand and see in action (via writing code) what a Functor is.

Functor Is a Container

Simply put, functor is a container that holds the value in it. We have seen this in the definition stating that Functor is a plain object. So let’s go ahead and create a simple container that can hold any value we pass onto it, and we call it a Container :

Listing 8-1. Container Definition
const Container = function(val) {
        this.value = val;
}

You might be wondering why we didn't write the Container function using our arrow syntax:

const Container = (val) => {
        this.value = val;
}

The above code will be fine, but the moment we try to apply new keyword on our Container, we will be getting the back an error like this:

Container is not a constructor(...)(anonymous function)

Why is that? Well, technically, in order to create a new Object, the function should have the internal method [[Construct]] and the property prototype. Sadly, the Arrow function doesn't have both! So here we are falling back to our old school friend function, which has the internal method [[Construct]], and it also has access to the prototype property.

Now with Container in place, we can go ahead and create a new object out of it:

Listing 8-2. Playing With Container
let testValue = new Container(3)
=> Container(value:3)


let testObj = new Container({a:1})
=> Container(value:{a:1})


let testArray = new Container([1,2])
=> Container(value:[1,2])

Nothing fancy here; Container is just holding the value inside it. We can pass any data type in JavaScript to it and Container will hold it. Before we move on, we can create a util method called of in the Container prototype, which will save us in writing the new keyword to create a new Container. The code looks like the following:

Listing 8-3. of method definition
Container.of = function(value) {
  return new Container(value);
}

With this of method in place, we can rewrite the above code (Listing 8-2) snippets like this:

Listing 8-4. Creating Container with of
testValue = Container.of(3)
=> Container(value:3)


testObj = Container.of({a:1})
=> Container(value:{a:1})


testArray = new Container([1,2])
=> Container(value:[1,2])

It is worth noting that Container can contain nested Containers too:

Container.of(Container.of(3));

is going to print:

Container {
        value: Container {
                value: 3
        }
}

Now that we have defined that the Functor is nothing but a Container that can hold the value, let’s revisit the definition of Functor.

Functor is a plain object (or type class in other languages) that implements the function map while running over each value in the object to produce a new object.

It looks like Functor needs to implement a method called map. Let’s implement that method in the next section.

Functor Implements Method Called map

Before we implement the map function , let’s stop here and think why we need map function in the first place. Remember that the created Container just holds the value that we pass onto it. But holding the value hardly has any use cases. And that is where map function comes into place. map function allows us to call any function on the value that is being currently held by the Container.

map function takes the value out of the Container and applies the passed function on that value and again put back the result in the Container. Let’s visualize using the following image (Figure 8-1):

A429083_1_En_8_Fig1_HTML.gif
Figure 8-1. Mechanism of Container And map Function

The above image tells the way map function is going to work with our Container object. It takes the value in the Container; in this case the value is 5, and pass on that value to the passed function double (this function just doubles the given number), and the result is being put back again to Container. With that understanding in place, we can implement the map function:

Listing 8-5. map function definition
Container.prototype.map = function(fn){
  return Container.of(fn(this.value));
}

As shown above, the above map function just simply does what we have discussed in our image! It’s simple and elegant! Now to make the point concrete, let’s put our image piece into code action:

let double = (x) => x + x;
Container.of(3).map(double)
=> Container { value: 6 }

Note that the map returns the result of the passed function again in the container, which allows us to chain the operation:

Container.of(3).map(double)
                           .map(double)
                           .map(double)


=> Container {value: 24}

Now implementing Container with our map function, we can make complete sense of Functor definition:

  • Functor is a plain object (or type class in other languages) that implements the function map that, while running over each value in the object to produce a new object.

Or in other words:

  • Functor is an object, which implement a map contract!

Well that’s Functor for you. But you might be wondering what Functor is useful for? We are going to answer that in the upcoming section.

Note

that Functor is a concept that looks for a contract. The contract as we have seen is simple, implement map! The way in which we implement map function provides different types of Functor like MayBe, Either, which we are going to discuss later in this chapter.

MayBe

We started the chapter with the argument of how we handle errors/exception using functional programming techniques. In the previous section we learned about the fundamental concept of Functor. In this section, we are going to see a type of Functor called as MayBe. The MayBe functor allows us to handle errors in our code in a more functional way.

Implementing MayBe

MayBe is a type of Functor, which means it’s going to implement a map function but in a different way. Let’s start with a simple MayBe, which can hold the data (very similar to Container implementation):

Listing 8-6. MayBe Function Definition
const MayBe = function(val) {
  this.value = val;
}


MayBe.of = function(val) {
  return new MayBe(val);
}

We just created MayBe, which resembles the Container implementation. Now as stated earlier, we have to implement a map contract for the MayBe, which looks like this:

Listing 8-7. MayBe’s map function definition
MayBe.prototype.isNothing = function() {
  return (this.value === null || this.value === undefined);
};


MayBe.prototype.map = function(fn) {
  return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this.value));
};

The map function does very similar things to the Container (simple Functor) map function. MayBe's map first checks whether the value in the container is null or undefined before applying the passed function using the isNothing function, which takes care of null/undefined checks:

(this.value === null || this.value === undefined);

Note that map puts back the result of applying the function back in the container:

return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));

Now it’s time to see MayBe in action.

Simple Use Cases

As we discussed in the previous section, MayBe does checks the null, undefined before applying the passed function in map. This is a very powerful abstraction that takes care of error handling! To make this concrete, here’s a simple example:

MayBe.of("string").map((x) => x.toUpperCase())

which returns

MayBe { value: 'STRING' }

The most important and interesting point to note here:

Listing 8-8. Creating our first MayBe
(x) => x.toUpperCase()

doesn't care if x is null or undefined or that it has been abstracted by the MayBe functor! What if the value of the string is null? Then the code looks like the following:

MayBe.of(null).map((x) => x.toUpperCase())

we will be getting back:

MayBe { value: null }

Wow, our code now doesn’t explode in null or undefined values as we have wrapped our value in the type safety container MayBe! We are now handling the null values in a declarative way.

Note

On MayBe.of(null) case, if we call map function , from our implementation we know that map first checks if the value is null or undefined by calling isNothing:

//implementation of map
MayBe.prototype.map = function(fn) {
  return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this.value));
};

if isNothing returns true. We return back MayBe.of(null) instead of calling the passed function!

In a normal imperative way, we would have done this:

let value = "string"
if(value != null || value != undefined)
        return value.toUpperCase();

The above code does exactly the same thing, but look at the steps required to check if the value is null or undefined, even for a single call! And also using MayBe, we don't care about those sneaky variables to hold the resulting value! Remember that we can chain our map function as desired:

Listing 8-9. Chaining with map
MayBe.of("George")
     .map((x) => x.toUpperCase())
     .map((x) => "Mr. " + x)

gives back:

MayBe { value: 'Mr. GEORGE' }

It’s so pleasant to watch! Before we close this section, we need to talk about two more important properties of MayBe.

The first one is that even if your passed function to map returns null/undefined, MayBe can take care of it! In other words, in the whole chain of map calls, it is fine if a function returns null or undefined. To illustrate the point, let’s tweak the last example:

MayBe.of("George")
     .map(() => undefined)
     .map((x) => "Mr. " + x)

Note that our second map function returns undefined; however, running the above code will give this result:

MayBe { value: null }

as expected!

The second important point is that all map functions will be called regardless if it receives null/undefined. We’ll pull out the same code snippet (Listing 8-9) that we have used in the previous example:

MayBe.of("George")
     .map(() => undefined)
     .map((x) => "Mr. " + x)

The point here is that even though the first map does return undefined:

map(() => undefined)

the second map will be called always (i.e., the chained maps to any level will be called always); it is just that the next map function in the chain returns undefined (as the previous map returns undefined/null), without applying the passed function! This process is repeated until the last map function call is evaluated in the chain.

Real-World Use Cases

Since MayBe is a type of container that can hold any values, it can also hold values of type Array. Imagine you have written an API to get the top 10 subreddit data based on types like top, new, hot:

Listing 8-10. Getting Top 10 SubReddit Posts
let getTopTenSubRedditPosts = (type) => {
    let response
    try{
       response = JSON.parse(request('GET',"https://www.reddit.com/r/subreddits/" + type + ".json?limit=10").getBody('utf8'))
    }catch(err) {
        response = { message: "Something went wrong" , errorCode: err['statusCode'] }
    }
    return response
}

request comes from the package sync-request. This will allow us to fire a request and get the response in synchronous fashion. This is just for illustration, and I don't recommend using synchronous calls in production.

getTopTenSubRedditPosts function just hits the URL and gets back the response. If there are any issues in hitting the reddit API, it sends back a custom response of the format:

. . .
response = { message: "Something went wrong" , errorCode: err['statusCode'] }
. . .

and if we call our API like this:

getTopTenSubRedditPosts('new')

we will be getting back the response in this format:

{"kind": "Listing", "data": {"modhash": "", "children": [], "after": null, "before": null}}

where children property will have an array of JSON objects. It will look something like this:

"{
  "kind": "Listing",
  "data": {
    "modhash": "",
    "children": [
      {
        "kind": "t3",
        "data": {
          . . .
          "url": "https://twitter.com/malyw/status/780453672153124864",
          "title": "ES7 async/await landed in Chrome",
          . . .
        }
      }
    ],
    "after": "t3_54lnrd",
    "before": null
  }
}"

From the response we need to return the array of JSON object that has the URL and title in it. Remember that if we pass an invalid subreddit type such as test to our getTopTenSubRedditPosts, it will return an error response that does not have a data or children property.

With MayBe in place, we can go ahead and implement the logic as such:

Listing 8-11. Getting Top 10 SubReddit Posts using MayBe
//arrayUtils from our library
import {arrayUtils} from '../lib/es6-functional.js'


let getTopTenSubRedditData = (type) => {
    let response = getTopTenSubRedditPosts(type);
    return MayBe.of(response).map((arr) => arr['data'])
                             .map((arr) => arr['children'])
                             .map((arr) => arrayUtils.map(arr,
                                (x) => {
                                    return {
                                        title : x['data'].title,
                                        url   : x['data'].url
                                    }
                                }
                            ))
}

Let's break down how getTopTenSubRedditData works. First we are wrapping the result of the reddit API call within the MayBe context using MayBe.of(response). Then we are running a series of functions using MayBe's map:

. . .
.map((arr) => arr['data'])
.map((arr) => arr['children'])
. . .

This will return the children array object from the response structure:

{"kind": "Listing", "data": {"modhash": "", "children": [ . . . .], "after": null, "before": null}}

And in the last map, we are using our own ArrayUtils's map to iterate over the children property and return only the title and URL as needed:

. . .
.map((arr) =>
        arrayUtils.map(arr,
    (x) => {
        return {
            title : x['data'].title,
            url   : x['data'].url
        }
    }
. . .

Now if we call our function with a valid reddit name like new:

getTopTenSubRedditData('new')

we get back the response:

MayBe {
  value:
   [ { title: '/r/UpliftingKhabre - The subreddit for uplifting and positive stories from India!',
       url: 'https://www.reddit.com/r/upliftingkhabre' },
     { title: '/R/JerkOffToCelebs - The Best Place To Jerk Off To Your Fave Celebs',
       url: 'https://www.reddit.com/r/JerkOffToCelebs' },
     { title: 'Angel Vivaldi channel',
       url: 'https://qa1web-portal.immerss.com/angel-vivaldi/angel-vivaldi' },
     { title: 'r/SuckingCock - Come check us out for INSANE Blowjobs! (NSFW)',
       url: 'https://www.reddit.com/r/suckingcock/' },
     { title: 'r/Just_Tits - Come check us out for GREAT BIG TITS! (NSFW)',
       url: 'https://www.reddit.com/r/just_tits/' },
     { title: 'r/Just_Tits - Come check us out for GREAT BIG TITS! (NSFW)',
       url: 'https://www.reddit.com/r/just_tits/' },
     { title: 'How to Get Verified Facebook',
       url: 'http://imgur.com/VffRnGb' },
     { title: '/r/TrollyChromosomes - A support group for those of us whose trollies or streetcars suffer from chronic genetic disorders',
       url: 'https://www.reddit.com/r/trollychromosomes' },
     { title: 'Yemek Tarifleri Eskimeyen Tadlarımız',
       url: 'http://otantiktad.com/' },
     { title: '/r/gettoknowyou is the ultimate socializing subreddit!',
       url: 'https://www.reddit.com/r/subreddits/comments/50wcju/rgettoknowyou_is_the_ultimate_socializing/' } ] }

The above response might not be the same for the readers, as the response will change from time to time.

The beauty of the getTopTenSubRedditData method is how it handles unexpected input that can cause null/undefined errors in our logic flow. What if someone calls your getTopTenSubRedditData with a wrong reddit type? Remember that it will return the JSON response from Reddit:

{ message: "Something went wrong" , errorCode: 404 }

That is, the datachildren property – will be empty! Let’s try this by passing the wrong reddit type and see how it responds:

getTopTenSubRedditData('new')

which returns:

MayBe { value: null }

without throwing any error! Even though our map function tries to get the data from the response (which is not present in this case), it returns MayBe.of(null), so the corresponding maps would not apply the passed function, as we have discussed earlier.

We can clearly sense how MayBe handled all the undefined/null errors with ease! Our getTopTenSubRedditData looks so declarative!

That’s all about the MayBe Functor. We are going to meet another functor in the next section called Either.

Either Functor

In this section we are going to create a new functor called Either. Either will allow us to solve the branching-out problem. To give a context, let’s see an example from the previous section (Listing 8-9):

MayBe.of("George")
     .map(() => undefined)
     .map((x) => "Mr. " + x)

The above code will return the result as:

MayBe {value: null}

as we would expect. But the question is, which branching (i.e., out of two map calls above) failed with undefined or null values. We can't answer this question easily with MayBe. The only way is to manually dig into the branching of MayBe and discover the culprit! This doesn't mean that MayBe has flaws, but just that in certain use cases, we need a better Functor than MayBe (mostly where you have many nested maps). This is where Either comes into the picture.

Implementing Either

We have seen the problem Either is going to solve for us; now let’s see its implementation :

Listing 8-12. Either Functor Parts Definition
const Nothing = function(val) {
  this.value = val;
};


Nothing.of = function(val) {
  return new Nothing(val);
};


Nothing.prototype.map = function(f) {
  return this;
};


const Some = function(val) {
  this.value = val;
};


Some.of = function(val) {
  return new Some(val);
};


Some.prototype.map = function(fn) {
  return Some.of(fn(this.value));
}

The implementation has two functions, namely, Some and Nothing. You can see that Some is just a copy of a Container with a name change. The interesting part is with Nothing. Nothing is also a Container, but its map doesn't run over a given function but rather just returns:

Nothing.prototype.map = function(f) {
  return this;
};

In other words, you can run your functions on Some but not on Nothing (not a technical statement right? :)

Here’s a quick example:

Some.of("test").map((x) => x.toUpperCase())
=> Some {value: "TEST"}


Nothing.of("test").map((x) => x.toUpperCase())
=> Nothing {value: "test"}

As shown in the above code snippet, calling map on Some runs over the passed function. However, in Nothing, it just returns the same value back test. And we will wrap these two objects into Either object like this:

Listing 8-13. Either Definition
const Either = {
  Some : Some,
  Nothing: Nothing
}

You might be wondering, what’s the usefulness of Some or Nothing. To understand this, let’s revisit our reddit example version of MayBe.

Reddit Example Either Version

The MayBe version of reddit example looks like (Listing 8-11):

let getTopTenSubRedditData = (type) => {
    let response = getTopTenSubRedditPosts(type);
    return MayBe.of(response).map((arr) => arr['data'])
                             .map((arr) => arr['children'])
                             .map((arr) => arrayUtils.map(arr,
                                (x) => {
                                    return {
                                        title : x['data'].title,
                                        url   : x['data'].url
                                    }
                                }
                            ))
}

on passing a wrong reddit type, say, for example, unknown:

getTopTenSubRedditData('unknown')
=> MayBe {value : null}

we get back MayBe of null value. But we didn't know the reason why null was returned! We know that getTopTenSubRedditData uses getTopTenSubRedditPosts to get the response. Now that Either in place, we can create a new version of getTopTenSubRedditPostsusing Either:

Listing 8-14. Get Top Ten Subreddit Using Either
let getTopTenSubRedditPostsEither = (type) => {

    let response
    try{
       response = Some.of(JSON.parse(request('GET',"https://www.reddit.com/r/subreddits/" + type + ".json?limit=10").getBody('utf8')))
    }catch(err) {
        response = Nothing.of({ message: "Something went wrong" , errorCode: err['statusCode'] })
    }
    return response
}

Note that we have wrapped the proper response with Some and error response with Nothing! Now with that in place, we can modify our reddit API to:

Listing 8-15. Get Top Ten Subreddit Using Either
let getTopTenSubRedditDataEither = (type) => {
    let response = getTopTenSubRedditPostsEither(type);
    return response.map((arr) => arr['data'])
                             .map((arr) => arr['children'])
                             .map((arr) => arrayUtils.map(arr,
                                (x) => {
                                    return {
                                        title : x['data'].title,
                                        url   : x['data'].url
                                    }
                                }
                            ))
}

The above code is just literally the MayBe version, but it’s just not using MayBe, rather it’s using Either's type.

Now let's call our new API with the wrong reddit data type:

getTopTenSubRedditDataEither('new2')

which will return:

Nothing { value: { message: 'Something went wrong', errorCode: 404 } }

This is so brilliant! Now with Either types in place, we get back the exact reason why our branching failed! As you can guess, getTopTenSubRedditPostsEither returns Nothing in case of an error (i.e., unknown reddit type); hence the mappings on getTopTenSubRedditDataEither will never happen since it is of type Nothing! You can sense how Nothing helped us in preserving the error message and also blocking the functions to map over!

On a closing note, we can try our new version with a valid reddit type:

getTopTenSubRedditDataEither('new')

It will give back the expected response in Some:

Some {
  value:
   [ { title: '/r/UpliftingKhabre - The subreddit for uplifting and positive stories from India!',
       url: 'https://www.reddit.com/r/upliftingkhabre' },
     { title: '/R/JerkOffToCelebs - The Best Place To Jerk Off To Your Fave Celebs',
       url: 'https://www.reddit.com/r/JerkOffToCelebs' },
     { title: 'Angel Vivaldi channel',
       url: 'https://qa1web-portal.immerss.com/angel-vivaldi/angel-vivaldi' },
     { title: 'r/SuckingCock - Come check us out for INSANE Blowjobs! (NSFW)',
       url: 'https://www.reddit.com/r/suckingcock/' },
     { title: 'r/Just_Tits - Come check us out for GREAT BIG TITS! (NSFW)',
       url: 'https://www.reddit.com/r/just_tits/' },
     { title: 'r/Just_Tits - Come check us out for GREAT BIG TITS! (NSFW)',
       url: 'https://www.reddit.com/r/just_tits/' },
     { title: 'How to Get Verified Facebook',
       url: 'http://imgur.com/VffRnGb' },
     { title: '/r/TrollyChromosomes - A support group for those of us whose trollies or streetcars suffer from chronic genetic disorders',
       url: 'https://www.reddit.com/r/trollychromosomes' },
     { title: 'Yemek Tarifleri Eskimeyen Tadlarımız',
       url: 'http://otantiktad.com/' },
     { title: '/r/gettoknowyou is the ultimate socializing subreddit!',
       url: 'https://www.reddit.com/r/subreddits/comments/50wcju/rgettoknowyou_is_the_ultimate_socializing/' } ] }

That’s all about Either.

If you are from a Java background, you can sense that Either is very similar to Optional in Java 8. In fact, Optional is a functor!

Word of Caution - Pointed Functor

Before we close the chapter, I need to make a point clear . In the beginning of the chapter we started saying that we created the of method just to escape the new keyword in place for creating Container. We did the same for MayBe and Either as well. To recall, Functor is just an interface that has a map contract. Pointed Functor is a subset of Functor, which has an interface that has a of contracts!

So what we have designed until now is called a Pointed Functor! This is just to make the terminologies right in the book! But you got to see what he problem does Functor or Pointed Functor solves for us in the real world, which is much important.

E6 adds Array.of making arrays a pointed functor!

Array.of("You are a pointed functor, too?")

Summary

We started our chapter with asking questions about how we will be handling exceptions in the functional programming world. We began with creating a simple Functor. We defined a Functor as being nothing but a container with a map function implemented. Then we went ahead and implemented a functor called MayBe. We saw how MayBe helps us in avoiding those pesky null/undefined checks. MayBe allowed us to write code in functional and declarative ways. Then we saw how Either helped us to preserve the error message while branching out. Either is just a supertype of Some and Nothing. Now we have seen Functors in action!