izuna/src/function.js
2025-04-17 12:52:29 -05:00

144 lines
3 KiB
JavaScript

import { curry, curryN } from './curry.js'
import { dispatch } from './fantasy-land.js'
import { concat as iconcat, iter } from './list.js'
/** @import { Fn, Morphism, InferredMorphism, Predicate } from './types.js' */
/**
* @template T
* @param {T} x
* @returns {x}
*/
export const id = x => x
/**
* @param {...Fn} fns
* @returns {(x: any) => any}
*/
export const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x)
export const concat = curry(dispatch(['fantasy-land/concat', 'concat'],
(b, a) => iconcat(iter(a), iter(b))
))
export const compose = curry(dispatch(['fantasy-land/compose', 'compose'],
/**
* @template T, U, V
* @param {Morphism<U, V>} f
* @param {Morphism<T, U>} 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<T>} pred
* @param {InferredMorphism<T>} pass
* @param {InferredMorphism<T>} fail
* @param {T} x
*/
(pred, pass, fail, x) => (
pred(x) ? pass(x) : fail(x)))
export const when = curry(
/**
* @template T
* @param {Predicate<T>} pred
* @param {InferredMorphism<T>} pass
* @param {T} x
*/
(pred, pass, x) => ifElse(pred, pass, id, x))
export const unless = curry(
/**
* @template T
* @param {Predicate<T>} pred
* @param {InferredMorphism<T>} fail
* @param {T} x
*/
(pred, fail, x) => ifElse(pred, id, fail, x))
/**
* @template A, B
* @template {Morphism<A, B>} [F=Morphism<A, B>]
* @typedef {(f: F, a: A) => B} StaticMorphism
*/
export const ap = curry(dispatch(['fantasy-land/ap', 'ap'],
/**
* @template A, B
* @template {Morphism<A, B>} Ap
* @param {Morphism<A, B>} f
* @param {{ ap: Ap } | Ap} a
*/
(f, a) => {
const fs = liftA(f)
const args = liftA(a)
const xs = fs.reduce((acc, f) => (
concat(acc, iter(map(f, args)))
), [])
return [...xs]
}))
export const chain = curry(dispatch(['fantasy-land/chain', 'chain'],
(f, a) => a.map(f)
))
export const map = curry(dispatch(['fantasy-land/map', 'map'],
(f, a) => a.map(f)
))
export const reduce = curry(dispatch(['fantasy-land/reduce', 'reduce'],
(f, acc, xs) => xs.reduce(f, acc)
))