We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies.

We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies. Less

We use cookies and other tracking technologies... More

Login or register
to apply for this job!

Login or register to start contributing with an article!

Login or register
to see more jobs from this company!

Login or register
to boost this post!

Show some love to the author of this blog by giving their post some rocket fuel 🚀.

Login or register to search for your ideal job!

Login or register to start working on this issue!

Engineers who find a new job through Functional Works average a 15% increase in salary 🚀

Blog hero image

Maybe monad in JavaScript to save us from the hell of the null guard clauses

Yan Yankowski 19 October, 2017 (3 min read)

If one were asked to choose a single functional data structure from all the available ones, I bet they would choose the redoubtable Maybe, or Option under its other incarnation in Scala. This Maybe monad is in essence a simple box of computation that checks whether the data that it stores inside has or has no defined value and, based on this, either continues to apply further transformations to the value within or simply ignores them and safely routes the absence of value toward the finale of the computation chain.

Example 1

Maybe.of(undefined)
  .map(value => value * 2)
  .map(value => value * value)
  .getOrElse(0) === 0 
Maybe.of(2)
  .map(value => value * 2)
  .map(value => value * value)
  .getOrElse(0) === 16

will both print true. The last method getOrElse(null), which simply unboxes the value or returns null if there is none. So we implement the null guard clause only once inside the map method without any need to have these null value checks scattered throughout all the callbacks passed to map in the above chain. Here is a short implementation.

Example 2

class Maybe {
    constructor(value) {
      this.__value = value;
    }
    static of(valueToBox){
      return new Maybe(valueToBox);
    }
    getOrElse(elseVal) {
      return this.isNothing() ? elseVal : this.__value;
    }
    isNothing() {
      return this.__value === null || this.__value === undefined;
    }
    map(fn) {  
      return this.isNothing()?       
               Maybe.of(null):
               Maybe.of(fn(this.__value));
    }
}

We have now contained all our null checking logic in one place, reducing the number of possible errors in the future.

Tip.
You’ll soon see a lot of getOrElse(null) and getOrElse([]) calls in your code. They can be optimized into:

...
getOrNull() {
    return this.getOrElse(null);
}
...

and

...
getOrEmptyArray() {
    return this.getOrElse([]);
}
...

There is one caveat, though. What if somewhere in the map chain we have a callback that returns another Maybe?

Example 3

const mbMapper = x => Maybe.of(x).map(_x => _x * 2); 
//-> returns a Maybe
Maybe.of(2).map(mbMapper).getOrElse(0) === ?

Our result is Maybe(4) We’d apparently need a second call to getOrElse() method to retrieve the value. Such constructions would soon pollute our code. Moreover it would effectively prevent our Maybe type from being a monad in the mathematical sense of the term. As a monad must flatten the monad hierarchy flowing through its chain. To tackle this problem we’ll introduce a new method: flatMap.

Example 4

...
flatMap(fn){
    if(this.isNothing()) return Maybe.Nothing();
    const m = fn(this.__value);

    return m.isNothing() ? 
           Maybe.Nothing() : 
           Maybe.of(m.__value);
}
...

Now we can do

const mbMapper = x => Maybe.of(x).map(_x => _x * 2);
Maybe.of(2).flatMap(mbMapper).getOrElse(0) === 4 // prints true

So the whole implementation of our Maybe monad looks now like:

Example 5

class Maybe {
    constructor(value) {
      this.__value = value;
    }
    static of(valueToBox){
      return new Maybe(valueToBox);
    }
    flatMap(fn){
      if(this.isNothing()) return Maybe.Nothing();
      const m = fn(this.__value);

      return m.isNothing() ? 
           Maybe.Nothing() : 
           Maybe.of(m.__value);
    }
    getOrElse(elseVal) {
      return this.isNothing() ? elseVal : this.__value;
    }
    getOrEmptyArray() {
      return this.getOrElse([]);
    }
    getOrNull() {
      return this.getOrElse(null);
    }
    isNothing() {
      return this.__value === null || this.__value === undefined;
    }
    map(fn) {  
      return this.isNothing()?       
               Maybe.of(null):
               Maybe.of(fn(this.__value));
    }
}

All that is left now is to ensure that we are indeed dealing with a monad here. To be considered as such our Maybe structure needs to satisfy the following laws (using chai testing library syntax):

  1. Left identity:
    it("left identity law should be satisfied", () => {
        const value = 2;
        const mbValue = Maybe.of(value);
        const mapper = x => Maybe.of(x * 2);
        expect(mbValue.flatMap(mapper)).to.deep.equal(mapper(value));
    });
    
  2. Right identity:
    it("right identity law should be satisfied", () => {
        const mbValue = Maybe.of(2);
        const mapper = x => Maybe.of(x);
        expect(mbValue.flatMap(mapper)).to.deep.equal(mbValue);
    });
    
  3. Associativity:
    it("associativity law should be satisfied", () => {
        const f = val => Maybe.of(val + 1);
        const g = val => Maybe.of(val * 2);
        const m = Maybe.of(1);
        const lhs = m.flatMap(f).flatMap(g);
        const rhs = m.flatMap(x => f(x).flatMap(g));
        expect(lhs).to.deep.equal(rhs);
    });
    
    The above tests prove that our Maybe is none other than a monad.
    If you're passionate about Front End development, check out our JavaScript job-board here!

Originally published on medium.com