UNPKG

functionalscript

Version:

FunctionalScript is a purely functional subset of JavaScript

172 lines (171 loc) 5.86 kB
import { identity, fn, compose } from "../function/module.f.js"; import { addition, logicalNot, strictEqual, stateScanToScan, foldToScan, reduceToScan, } from "../function/operator/module.f.js"; const fromArray = (array) => { const at = (i) => i < array.length ? { first: array[i], tail: () => at(i + 1) } : null; return at(0); }; export const concat = (head) => (tail) => tail === null ? head : ({ head, tail }); const trampoline = (list) => { while (typeof list === 'function') { list = list(); } return list; }; export const next = (head) => { let tail = null; while (true) { head = trampoline(head); if (head instanceof Array) { head = fromArray(head); } else if (head !== null && 'head' in head) { [head, tail] = [head.head, concat(head.tail)(tail)]; continue; } if (head !== null) { return { first: head.first, tail: concat(head.tail)(tail) }; } if (tail === null) { return null; } [head, tail] = [tail, null]; } }; export const iterable = (list) => ({ *[Symbol.iterator]() { let i = list; while (true) { const r = next(i); if (r === null) { return; } yield r.first; i = r.tail; } } }); const { from } = Array; export const toArray = (list) => { const u = trampoline(list); return u instanceof Array ? u : from(iterable(u)); }; const apply = (f) => (input) => () => { const n = next(input); if (n === null) { return null; } return f(n); }; const flatStep = ({ first, tail }) => concat(first)(flat(tail)); export const flat = apply(flatStep); const mapStep = (f) => ({ first, tail }) => ({ first: f(first), tail: map(f)(tail) }); export const map = f => apply(mapStep(f)); export const flatMap = f => compose(map(f))(flat); const filterStep = f => ({ first, tail }) => { const newTail = filter(f)(tail); return f(first) ? { first, tail: newTail } : newTail; }; export const filter = f => apply(filterStep(f)); const filterMapStep = f => n => { const [first, tail] = [f(n.first), filterMap(f)(n.tail)]; return first === null ? tail : { first, tail }; }; export const filterMap = f => apply(filterMapStep(f)); const takeWhileStep = f => ({ first, tail }) => f(first) ? { first, tail: takeWhile(f)(tail) } : null; export const takeWhile = f => apply(takeWhileStep(f)); const takeStep = n => ({ first, tail }) => 0 < n ? { first: first, tail: take(n - 1)(tail) } : null; export const take = n => apply(takeStep(n)); const dropWhileStep = f => ne => f(ne.first) ? dropWhile(f)(ne.tail) : ne; export const dropWhile = f => apply(dropWhileStep(f)); const dropStep = n => ne => 0 < n ? drop(n - 1)(ne.tail) : ne; export const drop = n => apply(dropStep(n)); export const first = def => input => { const ne = next(input); return ne === null ? def : ne.first; }; export const last = first => tail => { let i = { first, tail }; while (true) { const result = next(i.tail); if (result === null) { return i.first; } i = result; } }; export const find = def => f => compose(filter(f))(first(def)); export const some = find(false)(identity); export const isEmpty = fn(map(() => true)) .then(some) .then(logicalNot) .result; export const every = fn(map(logicalNot)) .then(some) .then(logicalNot) .result; export const includes = value => compose(map(strictEqual(value)))(some); export const countdown = count => () => { if (count <= 0) { return null; } const first = count - 1; return { first, tail: countdown(first) }; }; export const repeat = v => compose(countdown)(map(() => v)); export const cycle = list => () => { const i = next(list); return i === null ? null : { first: i.first, tail: concat(i.tail)(cycle(list)) }; }; const scanStep = op => ne => { const [first, newOp] = op(ne.first); return { first, tail: scan(newOp)(ne.tail) }; }; export const scan = op => apply(scanStep(op)); export const stateScan = op => compose(stateScanToScan(op))(scan); export const foldScan = op => compose(foldToScan(op))(scan); export const fold = op => init => compose(foldScan(op)(init))(last(init)); export const reduce = op => def => compose(scan(reduceToScan(op)))(last(def)); const lengthList = list => () => { const notLazy = trampoline(list); if (notLazy === null) { return null; } if (notLazy instanceof Array) { return [notLazy.length]; } const tail = lengthList(notLazy.tail); if ('first' in notLazy) { return { first: 1, tail }; } return { head: lengthList(notLazy.head), tail }; }; const sum = reduce(addition)(0); export const length = input => sum(lengthList(input)); const entryOperator = index => value => [[index, value], index + 1]; export const entries = input => { const o = entryOperator; return stateScan(o)(0)(input); }; const reverseOperator = first => tail => ({ first, tail }); export const reverse = fold(reverseOperator)(null); export const zip = a => b => () => { const aResult = next(a); if (aResult === null) { return null; } const bResult = next(b); if (bResult === null) { return null; } return { first: [aResult.first, bResult.first], tail: zip(aResult.tail)(bResult.tail) }; }; export const equal = e => { const f = a => b => () => { const [aResult, bResult] = [next(a), next(b)]; return aResult === null || bResult === null ? { first: aResult === bResult, tail: null } : { first: e(aResult.first)(bResult.first), tail: f(aResult.tail)(bResult.tail) }; }; return a => b => every(f(a)(b)); }; export const empty = null;