dimitris papadimitriou
dimitris papadimitriou More than 12 years’ experience as full stack developer and Software Architect . Functional javascript with categories.

Either Monad — A functional approach to Error handling in JS

Either Monad — A functional approach to Error handling in JS

In Error handling we have two possible paths either a computation succeeds or fails. The imperative way to control the flow is using exceptions and a try/catch block. In functional programming they recognized that those two paths ok or error can be joined into a structure that signifies one or the other as a possibility and so we can unify them into an **Either ** structure.

Either is a common type in functional Languages. Is commonly called a discriminated union. Which means an Either type can contain any of the types it sums up.

In order to find out whats inside the Either we pattern match. Usually in most functional libraries there would be a method that is called cata (also match or matchWith) that does exactly than :

Ok, Lets move one to how we could use it in error handling. The important thing in either Functor implementation is that by convention the left when mapped just ignores the mapping .

1
2
3
4
5
6
7
const right = (v) => ({ 
    map: (f) => right(f(v)),    
  });
  
const left = (v) => ({ 
    map: (_) => left(v),  
  });

this means that when we return a left then no more computations are executed with map.

we can also use the class notation in order to define the Either

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 class Either {  }

 class Right extends Either {
  constructor(value) {
   super();
   this.value = value;
  }

  map(f) {   return new Right(f(this.value))  }
  matchWith(pattern) {   return pattern.right(this.value)  }
 }

 class Left extends Either {
  constructor(value) {
   super();
   this.value = value;
  }

  map(f) {   return new Left(this.value);  }
  matchWith(pattern) {   return pattern.left(this.value)  }
 }

Refactoring Try / catch to Either

lets say we have this try catch block

1
2
3
4
5
6
7
8
9
var finalPrice;
   try {
      var discount = 0.1;
      finalPrice = 10 - discount * 10;
   } catch (e) {
      console.log(e);
   }if (finalPrice) {
      console.log(finalPrice);
   }

we want to remove the try/catch from the actual computations .

1
2
3
4
5
6
7
8
9
try {
      var computation = () => {
         var discount = 0.1;
         var finalPrice = 10 - discount * 10;
         return finalPrice;
      }
   } catch (e) {
      console.log(e);
   }

then we extract a method and pass the computation as a parameter

1
2
3
4
5
6
7
8
9
10
11
12
var Try = (f) => {
      try {
         return f();
      } catch (e) {
         console.log(e);
      }
   }
var finalPrice = Try(() => {
      var discount = 0.1;
      var finalPrice = 10 - discount * 10;
      return finalPrice;
   });

and finally we return an Either like that

1
2
3
4
5
6
7
8
9
10
11
var Try = (f) => {
      try { 
         return right(f());
      } catch (e) {
         return left(e);
      }
   }
Try(() => t / 2).matchWith({
      right: v => console.log(v),
      left: v => console.log("error : " + v)
   })

Either as a Monad

In the same way we can extend the Either functor to a monad by providing a valid bind method:

1
2
3
4
5
6
7
8
9
10
11
12
 const right = (v) => ({
   map: (f) => right(f(v)),
   bind: f => f(v),
   matchWith: (pattern) => pattern.right(v),  
  });

  const left = (v) => ({
   map: (_) => left(v),
   bind: f => left(v),
   matchWith: (pattern) => pattern.left(v),  
  });

The left always stops the computation and returns itself left(v). This means that for the four possible bind combinations between right and left again only in the case of right.bind(right ) we get a right. In all other cases the computation stops, and the first left is return

Either Monad Example

here we are going to extend the Example from the Maybe monad article ( you might want to check out Maybe monad first) but we are going to use Either instead of Maybe as a return type from the repositories by converting the Maybe to an Either using this method that we attach on the Maybe

1
2
3
4
5
6
 Maybe.prototype.ToEither = function (defaultLeft) {
  return this.matchWith({
   none: () => new Left(defaultLeft),
   some: (v) => new Right(v)
  })
 }

this is called natural transformation from the Maybe to Either in functional terminology but basically means that if we have a Maybe we can convert it in to an Either the only by passing a default value for our Left in case of the none .

we can use this in order to write something like this

1
2
3
4
 [{ id: 1, name: 'jim', age: 29 },
  { id: 2, name: 'jane', age: 25 }]
 .firstOrNone(employee => employee.id === id)
 .ToEither("no employee Found")

to get an Either.Left with some value that we want to pass around in the case where we didn’t found any value inside the array.

we can thus modify both the repositories to return an Either and the use the Bind of the Either to combine the results

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 var mockClientRepository = ({
    getById: (id) =>
      [{ id: 1, name: 'rick', age: 29, employeeId: 1 },
      { id: 2, name: 'morty', age: 25, employeeId: 3 }]
        .firstOrNone(client => client.id === id)
        .ToEither("no client Found")
  })

  var mockEmployeeRepository = ({
    getById: (id) =>
      [{ id: 1, name: 'jim', age: 29 },
      { id: 2, name: 'jane', age: 25 }]
        .firstOrNone(employee => employee.id === id)
        .ToEither("no employee Found")
  })

  var employeeNameDisplay = mockClientRepository
    .getById(2)
    .bind(client => mockEmployeeRepository.getById(client.employeeId))
    .matchWith({
      left: (error) => {
        return "error:" + error
      },
      right: (employee) => {
        return "employee name:" + employee.name
      }
    })

this is almost identical with the Maybe example but instead now we can be sure in which repository there was no element found during the search because we have used different string inside the Either.lefts

you can run the whole code and try some scenarios for ids that the client does not have an employee or there is no client at all. Think about how would the code look like if we hadn’t used Either and instead used Error codes or throwing exceptions.

comments powered by Disqus