functionalscript
Version:
FunctionalScript is a purely functional subset of JavaScript
172 lines (171 loc) • 5.86 kB
JavaScript
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;