monad-t 0.2.3

FlutureTMonetEither/index.js

'use strict';

const { prop, chain: chainR, pair, curry, always } = require('ramda');
const { isUndefined, isFunction, isGeneratorFunction } = require('ramda-adjunct');
const { of, map, chain } = require('fantasy-land');
const { Either, Identity } = require('monet');
const { Future, isFuture } = require('fluture');

const { aliasesForType } = require('../utils');

/**
 * @classdesc
 * FlutureTMonetEither is a transformer that wraps `monet.Either` into
 * `fluture.Future`.
 *
 * @description
 * Constructor for transforming monads. Call also be used as static function
 * without calling new statement.
 *
 * @class
 * @param {Identity|Either|Future} monad
 * @returns {FlutureTMonetEither}
 * @throws {Error}
 * @constructor
 *
 * @example
 *
 * EitherT(Identity.of(1)); //=> Either.Right(1)
 * EitherT(Either.Right(1)); //=> FlutureTMonetEither(1)
 * EitherT(Future.of(1)); //=> FlutureTMonetEither(1)
 * EitherT.of(Maybe.Some(1)); //=> EitherT<Maybe.Some(1)
 */
function FlutureTMonetEither(monad) {
  if (isUndefined(new.target)) {
    if (isFuture(monad)) {
      return FlutureTMonetEither.fromFuture(monad);
    } else if (monad instanceof Identity.fn.init) {
      return FlutureTMonetEither.fromValue(monad.get());
    } else if (monad instanceof Either.fn.init) {
      return FlutureTMonetEither.fromEither(monad);
    }
    throw new Error('FlutureTMonetEither can transform only specific monad types');
  }

  this.run = monad;
  this['@@type'] = this.constructor['@@type'];
}

/**
 * @type {string}
 * @example
 *
 * const { isFlutureTMonetEither } = require('monad-t/lib/FlutureTMonetEither/utils');
 * const FlutureTMonetEither = require('monad-t/lib/FlutureTMonetEither');
 *
 * isFlutureTMonetEither(FlutureTMonetEither.fromValue(1)); //=> true
 */
FlutureTMonetEither['@@type'] = 'FlutureTMonetEither';

/**
 * Creates a FlutureTMonetEither transformer from Either locked inside a Future.
 *
 * @method FlutureTMonetEither.of
 * @param {!Future} run Either wrapped in Future
 * @returns {FlutureTMonetEither}
 *
 * @example
 *
 * FlutureTMonetEither.of(Future.of(Either.Right(1))); //=> FlutureTMonetEither(1)
 */
FlutureTMonetEither[of] = function applicative(run) {
  return new this(run);
};

/**
 * Creates FlutureTMonetEither instance from a value, running a value through
 * the following composition: FlutureTMonetEither.of * Fluture.of * Either.Right
 *
 * @param {*} val
 * @returns {FlutureTMonetEither}
 *
 * @example
 *
 * FlutureTMonetEither.fromValue(1); //=> FlutureTMonetEither(1)
 */
FlutureTMonetEither.fromValue = function fromValue(val) {
  return this[of](Future.of(Either.Right(val)));
};

/**
 * Creates FlutureTMonetEither instance from an either, running it thought
 * the following composition: FlutureTMonetEither.of * Fluture.of
 *
 * @param {Either} either
 * @returns {FlutureTMonetEither}
 *
 * @example
 *
 * FlutureTMonetEither.fromEither(Either.Right(1)); //=> FlutureTMonetEither(1)
 */
FlutureTMonetEither.fromEither = function fromEither(either) {
  return this[of](Future.of(either));
};

/**
 * Creates FlutureTMonetEither instance from a future, transforming the value
 * locked inside the future into Either.Right, and then wrapping the future
 * into FlutureTMonetEither.
 *
 * @param {Future} future
 * @returns {FlutureTMonetEither}
 *
 * @example
 *
 * FlutureTMonetEither.fromFuture(Future.of(1)); //=> FlutureTMonetEither(1)
 */
FlutureTMonetEither.fromFuture = function fromFuture(future) {
  return this[of](future.map(Either.Right));
};

/**
 * Returns a Future which caches the resolution value of the given Future so that
 * whenever it's forked, it can load the value from cache rather than re-executing the chain.
 *
 * @param {FlutureTMonetEither} futureEither
 * @returns {FlutureTMonetEither}
 *
 * @example
 *
 * const loadDbRecordById = (id) => {
 *   console.log('Retrieving record from db');
 *   return DB.record.findById(id);
 * }
 *
 * const loadFromDb = FlutureTMonetEither.cache(
 *   FlutureTMonetEither.encaseP(loadSomethingFromDbById, 1)
 * );
 *
 * loadFromDb.fork(console.error, console.log);
 * //> "Retrieving record from db"
 * //> {...record...}
 *
 * loadFromDb.fork(console.error, console.log);
 * //> {...record...}
 */
FlutureTMonetEither.cache = function cache(futureEither) {
  return this.of(Future.cache(futureEither.run));
};

/**
 * Creates a Future which when forked runs all Futures in the given array in parallel,
 * ensuring no more than limit Futures are running at once.
 *
 * @param {number} runInParallelCount
 * @param {Array.<FlutureTMonetEither>} futureEitherList
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.parallel = function parallel(runInParallelCount, futureEitherList) {
  const futureList = futureEitherList
    .map(prop('run'))
    .map(chainR(
      (either) => {
        if (either.isRight()) {
          return Future.of(either.right());
        }
        return Future.reject(either.left());
      }
    ));
  return this.fromFuture(Future.parallel(runInParallelCount, futureList));
};

/**
 * Run two FlutureTMonetEithers in parallel. Basically like calling FlutureTMonetEither.parallel
 * with exactly two FlutureTMonetEithers.
 *
 * @param {FlutureTMonetEither} futureEitherA
 * @param {FlutureTMonetEither} futureEitherB
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.both = function both(futureEitherA, futureEitherB) {
  return this.parallel(2, pair(futureEitherA, futureEitherB));
};

/**
 * A specialized version of fantasy-do which works only for Futures, but has the advantage
 * of type-checking and not having to pass FlutureTMonetEither.of.
 * Another advantage is that the returned FlutureTMonetEither can be forked
 * multiple times, as opposed to with a general fantasy-do solution,
 * where forking the FlutureTMonetEither a second time behaves erroneously.
 *
 * Takes a function which returns an Iterator, commonly a generator-function,
 * and chains every produced FlutureTMonetEither over the previous.
 *
 * This allows for writing sequential asynchronous code without the pyramid of doom.
 * It's known as "coroutines" in Promise land, and "do-notation" in Haskell land.
 *
 * @param {Function} generator
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.do = function go(generator) {
  if (!isGeneratorFunction(generator)) {
    throw new Error('The only argument for Go constructor must be generator.');
  }

  return this.of(
    Future.do(function* gen() {
      const iterator = generator();

      let value;
      let state = iterator.next();

      while (!state.done) {
        value = yield state.value.run.chain((either) => {
          if (either.isLeft()) {
            return Future.reject(either.left());
          }

          return Future.of(either.right());
        });

        state = iterator.next(value);
      }

      return Either.of(state.value);
    })
  );
};

/**
 * Alias of `do`.
 *
 * @method
 * @static
 * @memberOf FlutureTMonetEither
 * @param {Function} generator
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.go = FlutureTMonetEither.do;

/**
 * Allows Promise-returning functions to be turned into FlutureTMonetEither-returning functions.
 *
 * Takes a function which returns a Promise, and a value, and returns a FlutureTMonetEither.
 * When forked, the FlutureTMonetEither calls the function with the value to produce the Promise,
 * and resolves with its resolution value, or rejects with its rejection reason.
 *
 * Partially applying encase with a function f allows us to create a "safe" version of f.
 * Instead of throwing exceptions, the encased version always returns a FlutureTMonetEither when
 * given the remaining argument(s).
 *
 * @param {Function} fn
 * @param {*} arg
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.encaseP = curry((fn, arg) => FlutureTMonetEither.of(
  Future.encaseP(fn, arg).map(Either.Right)
));

/**
 * Binary version of `encaseP`.
 *
 * @param {Function} fn
 * @param {*} arg1
 * @param {*} arg2
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.encaseP2 = curry((fn, arg1, arg2) => FlutureTMonetEither.of(
  Future.encaseP2(fn, arg1, arg2).map(Either.Right)
));

/**
 * Ternary version of `encaseP`.
 *
 * @param {Function} fn
 * @param {*} arg1
 * @param {*} arg2
 * @param {*} arg3
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.encaseP3 = curry((fn, arg1, arg2, arg3) => FlutureTMonetEither.of(
  Future.encaseP3(fn, arg1, arg2, arg3).map(Either.Right)
));

/**
 * Create a FlutureTMonetEither which when forked spawns a Promise using
 * the given function and resolves with its resolution value, or rejects with its rejection reason.
 *
 * @param {Function} fn
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.tryP = function tryP(fn) {
  return this.encaseP(fn, undefined);
};

/**
 * Transforms the resolution value inside the FlutureTMonetEither, and returns
 * a new FlutureTMonetEither with the transformed value.
 * This is like doing promise.then(x => x + 1), except that it's lazy,
 * so the transformation will not be applied before the FlutureTMonetEither is forked.
 * The transformation is only applied to the resolution branch:
 * If the FlutureTMonetEither is rejected, the transformation is ignored. *
 *
 * @method map
 * @memberOf FlutureTMonetEither
 * @instance
 * @param {Function} fn
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.prototype[map] = function functor(fn) {
  return this.constructor.of(
    this.run[map](
      either => either.map(fn)
    )
  );
};

/**
 * Allows the creation of a new FlutureTMonetEither based on the resolution value.
 * This is like doing promise.then(x => Promise.resolve(x + 1)), except that it's lazy,
 * so the new FlutureTMonetEither will not be created until the other one is forked.
 * The function is only ever applied to the resolution value;
 * it's ignored when the FlutureTMonetEither was rejected.
 * To learn more about the exact behaviour of chain.
 *
 * @method chain
 * @memberOf FlutureTMonetEither
 * @instance
 * @param {Function} fn
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.prototype[chain] = function bind(fn) {
  return this.constructor.of(
    this.run[chain](
      (either) => {
        if (either.isRight()) {
          return fn(either.right()).run;
        }
        return this.run.constructor.of(either);
      }
    )
  );
};

/**
 * Execute the computation that was passed to the FlutureTMonetEither
 * at construction using the given reject and resolve callbacks.
 *
 * @param {Function} leftFn
 * @param {Function} rightFn
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.prototype.fork = function fork(leftFn, rightFn) {
  return this.run.fork(
    leftFn,
    either => either.cata(leftFn, rightFn)
  );
};

/**
 * Returns a new FlutureTMonetEither which either rejects with the first rejection reason,
 * or resolves with the last resolution value once and if both FlutureTMonetEither resolve.
 * This behaves analogously to how JavaScript's and-operator does.
 *
 * @param {FlutureTMonetEither} futureEither
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.prototype.and = function and(futureEither) {
  return this.constructor.of(
    Future
      .of(either1 => either2 => either1.chain(always(either2)))
      .ap(this.run)
      .ap(futureEither.run)
  );
};

/**
 * Method for isolating asynchronous side effects.
 *
 * @param {Function} fn The function returning new instance of FlutureTMonetEither.
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.prototype.tapF = function tapF(fn) {
  return this[chain](val => fn(val).and(this.constructor.fromValue(val)));
};

/**
 * Method for isolating side effects.
 *
 * @param {Function} fn
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.prototype.tap = function tap(fn) {
  return this[map]((val) => {
    fn(val);
    return val;
  });
};

/**
 * Method for chaining inner Either with an either returned by
 * the supplied `fn`.
 *
 * @param {Function} fn The function generating new Either
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.prototype.chainEither = function chainEither(fn) {
  return this[chain](val => this.constructor.of(Future.of(fn(val))));
};

/**
 * Method for chaining inner Future with an Future returned by
 * the supplied `fn`. Future instance should not have value inside it, not Either.
 *
 * @param {Function} fn The function generating new Future
 * @returns {FlutureTMonetEither}
 */

FlutureTMonetEither.prototype.chainFuture = function chainFuture(fn) {
  return this.chain(v => this.constructor.fromFuture(fn(v)));
};

/**
 * Very similar to the filtering of a list, filter on FlutureTMonetEither
 * will filter out any elements that do not meet the predicate.
 *
 * @param {Function} predicate
 * @param {Function|*} fnOrValue
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.prototype.filter = function filter(predicate, fnOrValue) {
  return this[chain]((val) => {
    if (!predicate(val)) {
      if (isFunction(fnOrValue)) {
        return this.constructor.fromEither(Either.Left(fnOrValue(val)));
      }
      return this.constructor.fromEither(Either.Left(fnOrValue));
    }
    return this.constructor.fromValue(val);
  });
};

/**
 * Map over the rejection reason of the FlutureTMonetEither.
 * This is like map, but for the rejection branch.
 *
 * @param {Function} fn
 * @returns {FlutureTMonetEither}
 */
FlutureTMonetEither.prototype.mapRej = function mapRej(fn) {
  return this.constructor.of(this.run.mapRej(fn));
};

/**
 * An alternative way to fork the FlutureTMonetEither.
 * This eagerly forks the FlutureTMonetEither and
 * returns a Promise of the result. This is useful if some API wants you
 * to give it a Promise. It's the only method which forks the Future without
 * a forced way to handle the rejection branch, so I recommend against
 * using it for anything else.
 *
 * @returns {Promise}
 */
FlutureTMonetEither.prototype.promise = function promise() {
  return new Promise((resolve, reject) => this.fork(reject, resolve));
};


aliasesForType(FlutureTMonetEither);


module.exports = FlutureTMonetEither;