Michael
Michael

Reputation: 521

Either Monad: How to collect all the Right values and do work with all of them at the end?

So what I am trying to do is collect all the Right values from a series of Either results and have all of them available at the end of the chain to do work on them. I also want the chain to fail fast if one of the Either values is a Left.

So after reading around I believe the key to getting this all to work is a combination of a curried function and an applicative functor.

Below is sample code I have so far that is not quite working. Note that I am using monet.js and lodash:

const test1 = Right('test1');
const test2 = Left('test2');
const test3 = Right('test3');

function success(val, val2, val3){
    return {
        val, val2, val3
    };
}

const curriedFn = _.curry(success);

Right(curriedFn)
    .ap(Right(function(fn){
        return fn(test1);
    }))
    .ap(Right(function(fn){
        return fn(test1);
    }))
    .ap(Right(function(fn){
        return fn(test1);
    }))
    .map(function(res){
        console.log(res);
    });

At the very end I get an object that contains 3 Either values that looks like this:

{ val: { isRightValue: true, value: 'test1' },
  val2: { isRightValue: true, value: 'test1' },
  val3: { isRightValue: true, value: 'test1' } }

What I want is the 3 actual values. And if you see, one of the Either values is a Left and the chain should have been broken.

I am trying to do this in a pure functional way. That is why I am not mapping and stuffing values in an object out of the scope of the functions.

Any ideas? Alternatives?

Upvotes: 1

Views: 823

Answers (2)

Michael
Michael

Reputation: 521

So I misread the docs: https://monet.github.io/monet.js/#maybe

You need to nest the successive .ap calls. Below is a reworked example of what I was trying to do above:

const test1 = Right('test1');
const test2 = Right('test2');
const test3 = Right('test3');

const success = _.curry(function (val, val2, val3){
    return {
        val,
        val2,
        val3
    };
});

test3.ap(test2.ap(test1.map(success)))
    .map(success => {
        console.log(success)
    });

I am sure there is a elegant way with compose or some other monad to flatten the chain out, but for the time being I am satisfied.

Upvotes: 1

Mulan
Mulan

Reputation: 135247

It looks like you're using .ap incorrectly

const Either =
  require ('data.either')

const { Left, Right } =
  Either

const success = x => y => z =>
  [ x, y, z ]

const a =
  Right (1)

const b =
  Right (2)

const c =
  Right (3)

const badegg =
  Left ('badegg')

If success is applied to badegg for any param, the immediate result will be a Left. Subsequent calls to .ap will not affect the Left

Right (success)
  .ap (a)
  .ap (b)
  .ap (c)
  .fold (console.error, console.log) // [ 1, 2, 3 ]

Right (success)
  .ap (badegg)
  .ap (b)
  .ap (c)
  .fold (console.error, console.log) // "badegg"

Right (success)
  .ap (a)
  .ap (badegg)
  .ap (c)
  .fold (console.error, console.log) // "badegg"

Right (success)
  .ap (a)
  .ap (b)
  .ap (badegg)
  .fold (console.error, console.log) // "badegg"

Upvotes: 1

Related Questions