import { curry, curryN } from './curry.js' import { dispatch } from './fantasy-land.js' import { head, isIterable, concatIter, iter, tail } from './list.js' /** @import { Fn, Morphism, InferredMorphism, Predicate } from './types.js' */ /** * @template T * @param {T} x * @returns {x} */ export const id = x => x const bareValue = x => Array.isArray(x) || typeof x === 'string' || !isIterable(x) export const concat = dispatch(['fantasy-land/concat', 'concat'], (b, a) => { if (Array.isArray(a) && bareValue(b)) { return a.concat(b) } else if (Array.isArray(b) && bareValue(a)) { return b.concat(a) } else { return concatIter(iter(a), iter(b)) } }) export const compose = dispatch(['fantasy-land/compose', 'compose'], /** * @template T, U, V * @param {Morphism} f * @param {Morphism} g * @param {T} x * @returns V */ (f, g, x) => chain(g, chain(f, x)) ) /** * @template T * @param {T | T[]} a * @Returns {T[]} */ export const liftA = a => Array.isArray(a) ? a : [a] /** * @template T, U, R * @param {(x: T, y: U) => R} binary * @param {T} x * @param {U} y * @returns {R} */ export const liftF = curry((binary, x, y) => binary(x, y)) /** * @param {number} a * @param {number} b * @returns {number} */ export const add = (a, b) => a + b /** * @template T * @param {T} x * @returns {() => T} */ export const thunk = x => () => x /** * @template T, U * @template {(a: T, b: U, ...rest: any[]) => any} F * @param {F} fn * @returns {(b: U, a: T, ...rest: any[]) => any} */ export function flip(fn) { return curryN(fn.length, function(a, b) { const args = Array.prototype.slice.call(arguments, 0) args[0] = b args[1] = a return fn.apply(this, args) }) } export const ifElse = curry( /** * @template T * @param {Predicate} pred * @param {InferredMorphism} pass * @param {InferredMorphism} fail * @param {T} x */ (pred, pass, fail, x) => ( pred(x) ? pass(x) : fail(x))) export const when = curry( /** * @template T * @param {Predicate} pred * @param {InferredMorphism} pass * @param {T} x */ (pred, pass, x) => ifElse(pred, pass, id, x)) export const unless = curry( /** * @template T * @param {Predicate} pred * @param {InferredMorphism} fail * @param {T} x */ (pred, fail, x) => ifElse(pred, id, fail, x)) /** * @template A, B * @template {Morphism} [F=Morphism] * @typedef {(f: F, a: A) => B} StaticMorphism */ export const ap = dispatch(['fantasy-land/ap', 'ap'], /** * @template A, B * @template {Morphism} Ap * @param {Ap} f * @param {{ ap: Ap } | Ap} a */ (f, a) => { const fs = liftA(f) const args = liftA(a) const xs = fs.reduce((acc, f) => ( concat(acc, map(f, args)) ), []) return [...xs] }) export const applyTo = flip(ap) export const chain = dispatch(['fantasy-land/chain', 'chain'], (f, a) => f(a) ) export const map = dispatch(['fantasy-land/map', 'map'], (f, a) => chain(f, a) ) export const reduce = dispatch(['fantasy-land/reduce', 'reduce'], (f, acc, xs) => xs.reduce(f, acc) ) export const repeat = curry((n, x) => { const results = [] for (let i = n; i > 0; i--) { results.push(x) } return results }) export const tee = curry((f, x) => { f(x) return x }) /** * @param {...Fn} fns * @returns {(x: any) => any} */ export const pipe = (...fns) => (...args) => reduce((v, f) => f(v), head(fns)(...args), tail(fns))