Source: applicative.js

/**
 * maryamyriameliamurphies.js
 * A library of Haskell-style morphisms ported to ES2015 JavaScript.
 *
 * applicative.js
 *
 * @file Applicative type class.
 * @license ISC
 */

/** @module applicative */

import {
  partial,
  flip,
  id,
  constant
} from './base';

import {fmap} from './functor';

import {
  defines,
  dataType
} from './type';

import {error} from './error';

/**
 * `Applicative` functors are functors that support function application within their contexts. They
 * must define `pure` and `ap` methods and also be instances of `Functor`.
 * @param {*} - Any object
 * @returns {boolean} `true` if an object is an instance of `Applicative` and `false` otherwise
 * @kind function
 */
export const Applicative = defines(`fmap`, `pure`, `ap`);

/**
 * Lift a value into an applicative context.
 * <br>`Haskell> pure :: a -> f a`
 * @param {Object} f - An applicative functor
 * @param {*} a - Any object
 * @returns {Object} An applicative functor with the value injected
 * @kind function
 * @example
 * const lst = list(1,2,3); // => [1:2:3:[]]
 * pure(lst, 5);            // => [5:[]]
 */
export const pure = (f, a) => {
  const pure_ = (f, a) => Applicative(f) ? dataType(f).pure(a) : error.typeError(f, pure);
  return partial(pure_, f, a);
}

/**
 * Apply a function within an applicative context to an applicative functor.
 * <br>`Haskell> (<*>) :: f (a -> b) -> f a -> f b`
 * @param {Function} f - A function lifted into an applicative context
 * @param {Object} a - An applicative functor
 * @returns {Object} A new applicative functor of the same type, the result of the application
 * @kind function
 * @example
 * const lst = list(1,2,3);
 * const p = pure(lst, id);     // lift id function into applicative context
 * ap(p, lst);                  // => [1:2:3:[]] // proves identity
 * const f = x => x * 10;
 * const g = x => x * 3;
 * const pf = pure(lst, f);
 * const pg = pure(lst, g);
 * const p$ = pure(lst, $);
 * ap(ap(ap(p$)(pf))(pg))(lst); // => [30:60:90:[]] // not pretty
 * ap(ap(ap(p$, pf), pg), lst); // => [30:60:90:[]] // but
 * ap(pf, ap(pg, lst));         // => [30:60:90:[]] // proves composition
 * ap(pf, pure(lst, 10));       // => [100:[]]
 * pure(lst, f(10));            // => [100:[]] // proves homomorphism
 * ap(pf, pure(lst, 3));        // => [30:[]]
 * const a = pure(lst, 3) ;
 * ap(pf, a);                   // => [30:[]] // proves interchange (not actually possible?)
 */
export const ap = (f, a) => {
  const ap_ = (f, a) => {
    if (Applicative(f) === false) { error.typeError(f, ap); }
    if (Applicative(a) === false) { error.typeError(a, ap); }
    return dataType(a).ap(f, a);
  }
  return partial(ap_, f, a);
}

/**
 * A variant of `ap` with the arguments reversed.
 * <br>`Haskell> (<**>) :: Applicative f => f a -> f (a -> b) -> f b`
 * @param {Function} f - A function lifted into an applicative context
 * @param {Object} a - The first argument to f
 * @param {Object} b - The second argument to f
 * @returns {Object} A new applicative functor of the same type, the result of the application
 * @kind function
 * @example
 * const lst1 = list(1,2,3);
 * const lst2 = list(10,10,10);
 * const f1 = (x, y) => x * y;
 * const f2 = (x, y) => x + y;
 * const f3 = (x, y) => x - y;
 * apFlip(f1, lst1, lst2);      // => [10:10:10:20:20:20:30:30:30:[]]
 * apFlip(f2, lst1, lst2);      // => [11:11:11:12:12:12:13:13:13:[]]
 * apFlip(f3, lst1, lst2);      // => [9:9:9:8:8:8:7:7:7:[]]
 */
export const apFlip = (f, a, b) => {
  const apFlip_ = (f, a, b) => liftA2(flip(f), a, b);
  return partial(apFlip_, f, a, b);
}

/**
 * Sequence actions, discarding the value of the first argument.
 * <br>`Haskell> (*>) :: f a -> f b -> f b`
 * @param {Object} a1 - The action to skip
 * @param {Object} a2 - The action to perform
 * @returns {Object} A new applicative functor, the result of sequencing the actions
 * @kind function
 * @example
 * const l1 = list(1,2,3);
 * const l2 = list(4,5,6);
 * then(l1, l2);           // => [4:5:6:4:5:6:4:5:6:[]]
 */
export const then = (a1, a2) => {
  const then_ = (a1, a2) => liftA2(constant(id), a1, a2);
  return partial(then_, a1, a2);
}

/**
 * Sequence actions, discarding the value of the second argument.
 * <br>`Haskell> (<*) :: f a -> f b -> f a`
 * @param {Object} a1 - The action to perform
 * @param {Object} a2 - The action to skip
 * @returns {Object} A new applicative functor, the result of sequencing the actions
 * @kind function
 * @example
 * const l1 = list(1,2,3);
 * const l2 = list(4,5,6);
 * skip(l1, l2);           // => [1:1:1:2:2:2:3:3:3:[]]
 */
export const skip = (a1, a2) => {
  const skip_ = (a1, a2) => liftA2(constant, a1, a2);
  return partial(skip_, a1, a2);
}

/**
 * Lift a function into an applicative context.
 * <br>`Haskell> liftA :: Applicative f => (a -> b) -> f a -> f b`
 * @param {Function} f - The function to lift into an applicative context
 * @param {Object} a - An applicative functor, the context to lift the function into
 * @returns {Object} The result of applying the lifted function
 * @kind function
 * @example
 * const lst = list(1,2,3);
 * const mb = just(1);
 * const f = x => x * 10;
 * liftA(f, lst);           // => [10:20:30:[]]
 * liftA(f, mb);            // => Just 10
 */
export const liftA = (f, a) => {
  const liftA_ = (f, a) => ap(dataType(a).pure(f))(a);
  return partial(liftA_, f, a);
}

/**
 * Lift a binary function to actions.
 * <br>`Haskell> liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c`
 * @param {Function} f - The function to lift into an applicative context
 * @param {Object} a - An applicative functor, the first argument to f
 * @param {Object} b - An applicative functor, the second argument to f
 * @returns {Object} The result of applying the lifted function
 * @kind function
 * @example
 * const mb1 = just(1);
 * const mb2 = just(10);
 * const lst1 = list(1,2,3);
 * const lst2 = list(10,10,10);
 * const f = (x, y) => {
 *   const k1_ = (x, y) => x * y;
 *   return partial(k1_, x, y);
 *  }
 * liftA2(f, mb1, mb2);           // => Just 10
 * liftA2(f, lst1, lst2);         // => [10:10:10:20:20:20:30:30:30:[]]
 */
export const liftA2 = (f, a, b) => {
  const liftA2_ = (f, a, b) => ap(fmap(f, a))(b);
  return partial(liftA2_, f, a, b);
}

// /**
//  * Lift a ternary function to actions.
//  * <br>`Haskell> liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d`
//  * @param {Function} f - The function to lift into an applicative context
//  * @param {Object} a - An applicative functor, the first argument to f
//  * @param {Object} b - An applicative functor, the second argument to f
//  * @param {Object} c - An applicative functor, the third argument to f
//  * @returns {Object} The result of applying the lifted function
//  * @kind function
//  */
// export const liftA3 = (f, a, b, c) => {
//   const liftA3_ = (f, a, b, c) => ap(ap(fmap(f, a))(b))(c);
//   return partial(liftA3_, f, a, b, c);
// }