© Anto Aravinth, Srikanth Machiraju 2018
Anto Aravinth and Srikanth MachirajuBeginning Functional JavaScripthttps://doi.org/10.1007/978-1-4842-4087-8_8

8. Fun with Functors

Anto Aravinth1  and Srikanth Machiraju2
(1)
Chennai, Tamil Nadu, India
(2)
Hyderabad, Andhra Pradesh, India
 

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

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

Note

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

Once you check out the code, please check out branch chap08:

...

git checkout -b chap08 origin/chap08

...

For running the codes, as before run:

...

npm run playground

...

What Is a Functor?

In this section we are going to see what a functor really is. Here is its definition:
  • A 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, produces a new object.

It is not that easy to understand the definition at first sight. We are going to break it down step by step so that we clearly understand it and see in action (via writing code) what a functor is.

Functor Is a Container

Simply put, a functor is a container that holds the value in it. We have seen this in the definition stating that functor is a plain object. Let’s create a simple container that can hold any value we pass into it, and call it a Container (see Listing 8-1).
const Container = function(val) {
        this.value = val;
}
Listing 8-1

Container Definition

Note

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

const Container = (val) => {

this.value = val;

}

That code will be fine, but the moment we try to apply the new keyword on our Container, we will get an error like this:

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

Why is that? Well, technically, 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 friend function, which has the internal method [[Construct]], and it also has access to the prototype property.

Now with Container in place, we can create a new object out of it, as shown in Listing 8-2.
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])
Listing 8-2

Playing With Container

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 Listing 8-3.
Container.of = function(value) {
  return new Container(value);
}
Listing 8-3

of Method Definition

With this of method in place, we can rewrite the code in Listing 8-2 as shown in Listing 8-4.
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])
Listing 8-4

Creating Container with of

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 a 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.

Implementing map

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

The map function takes the value out of the Container , applies the passed function on that value, and again puts the result back in the Container. Let’s visualize using the image shown in Figure 8-1.
../images/429083_2_En_8_Chapter/429083_2_En_8_Fig1_HTML.jpg
Figure 8-1

 Mechanism of Container and map function

Figure 8-1 shows the way the 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 passes on that value to the passed function double (this function just doubles the given number). The result is put back again into the Container. With that understanding in place, we can implement the map function, as shown in Listing 8-5.
Container.prototype.map = function(fn){
  return Container.of(fn(this.value));
}
Listing 8-5

map Function Definition

As shown earlier, the preceding map function simply does what we have discussed in Figure 8-1. 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 the 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, produces a new object.

Or in other words:
  • Functor is an object that implements a map contract.

Now that we have defined it, you might be wondering what functor is useful for. We are going to answer that in the next section.

Note

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

MayBe

We started the chapter with the argument of how we handle errors and 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 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 in Listing 8-6, which can hold the data (very similar to a Container implementation):
const MayBe = function(val) {
  this.value = val;
}
MayBe.of = function(val) {
  return new MayBe(val);
}
Listing 8-6

MayBe Function Definition

We just created MayBe, which resembles the Container implementation. As stated earlier, we have to implement a map contract for the MayBe, which looks like Listing 8-7.
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));
};
Listing 8-7

MayBe’s map Function Definition

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 and undefined checks:
(this.value === null || this.value === undefined);
Note that map puts 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 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, a simple example is provided in Listing 8-8.
MayBe.of("string").map((x) => x.toUpperCase())
Listing 8-8

Creating our First MayBe

which returns
MayBe { value: 'STRING' }
The most important and interesting point to note here is this:
(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 this:
MayBe.of(null).map((x) => x.toUpperCase())
We will be getting back this:
MayBe { value: null }

Now our code 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 the 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 preceding 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. 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, as shown in Listing 8-9.
MayBe.of("George")
     .map((x) => x.toUpperCase())
     .map((x) => "Mr. " + x)
Listing 8-9

Chaining with map

gives back:
MayBe { value: 'Mr. GEORGE' }
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 preceding code will give this result:
MayBe { value: null }

as expected.

The second important point is that all map functions will be called regardless if they receive null/undefined. We’ll pull out the same code snippet (Listing 8-9) that we 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

Because 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, and hot (see Listing 8-10).
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
}
Listing 8-10

Getting Top 10 SubReddit Posts

Note

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; we don’t recommend using synchronous calls in production.

The getTopTenSubRedditPosts function just hits the URL and gets the response. If there are any issues in hitting the Reddit API, it sends back a custom response of this format:
. . .
response = { message: "Something went wrong" , errorCode: err['statusCode'] }
. . .
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 the 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 shown in Listing 8-11.
//arrayUtils from our library
import {arrayUtils} from '../lib/es8-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
                                    }
                                }
                            ))
}
Listing 8-11

Getting Top 10 SubReddit Posts Using MayBe

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}}
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 this response:
MayBe {
  value:
   [ { title: '/r/UpliftingKhabre - The subreddit for uplifting and positive stories from India!',
       url: 'https://www.reddit.com/r/ },
     { title: '/R/JerkOffToCelebs - The Best Place To Off To Your Fave Celebs',
       url: 'https://www.reddit.com/r/ },
     { title: 'Angel Vivaldi channel',
       url: 'https://qa1web-portal.immerss.com/angel-vivaldi/angel-vivaldi' },
     { title: 'r/test12 - Come check us out for INSANE',
       url: 'https://www.reddit.com/r/' },
     { title: 'r/Just - Come check us out for GREAT',
       url: 'https://www.reddit.com/r/just/' },
     { title: 'r/Just - Come check us out for GREAT',
       url: 'https://www.reddit.com/r/just/' },
     { 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/' } ] }

Note

The 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. 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 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, which will allow us to solve the branching-out problem. To provide a context, let’s revisit an example from the previous section (Listing 8-9):
MayBe.of("George")
     .map(() => undefined)
     .map((x) => "Mr. " + x)
This code will return the result
MayBe {value: null}

as we would expect. However, the question is which branching (i.e., out of two earlier map calls) failed with undefined or null values. We cannot 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).
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));
}
Listing 8-12

Either Functor Parts Definition

The implementation has two functions, 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 this:
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 preceding code snippet, calling map on Some runs over the passed function. However, in Nothing, it just returns the same value, test. We wrap these two objects into the Either object as shown in Listing 8-13.
const Either = {
  Some : Some,
  Nothing: Nothing
}
Listing 8-13

Either Definition

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

Reddit Example Either Version

The MayBe version of the Reddit example looks like this (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 why null was returned. We know that getTopTenSubRedditData uses getTopTenSubRedditPosts to get the response. Now that Either is in place, we can create a new version of getTopTenSubRedditPosts using Either, as shown in Listing 8-14.
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
}
Listing 8-14

Get Top Ten Subreddit Using Either

Note that we have wrapped the proper response with Some and the error response with Nothing. Now with that in place, we can modify our Reddit API to the code shown in Listing 8-15.
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
                                    }
                                }
                            ))
}
Listing 8-15

Get Top Ten Subreddit Using Either

This 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')
This 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 because 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 return 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/ },
     { title: '/R/ - The Best Place To Off To Your Fave,
       url: 'https://www.reddit.com/r/ },
     { title: 'Angel Vivaldi channel',
       url: 'https://qa1web-portal.immerss.com/angel-vivaldi/angel-vivaldi' },
     { title: 'r/test12 - Come check us out for INSANE',
       url: 'https://www.reddit.com/r/ /' },
     { title: 'r/Just - Come check us out for',
       url: 'https://www.reddit.com/r/just/' },
     { title: 'r/Just - Come check us out for',
       url: 'https://www.reddit.com/r/' },
     { 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 .

Note

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, we 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 an of contract.

What we have designed thus far is called a pointed functor. This is just to make the terms right in the book, but you got to see what problem functor or pointed functor solves for us in the real world, which is more important.

Summary

We started our chapter by 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 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.