/**
* This file contains the Free algebraic data type. Free is a data type that is
* used primarily to create a Combinable for any given data structure. It is
* useful when one wants to use combine things without deciding on a specific
* data structure to implement.
*
* @experimental
* @module Free
* @since 2.0.0
*/
import type { Kind, Out } from "./kind.ts";
import type { Combinable } from "./combinable.ts";
import { flow, pipe } from "./fn.ts";
/**
* @since 2.0.0
*/
export type Node = {
readonly tag: "Node";
readonly value: A;
};
/**
* @since 2.0.0
*/
export type Link = {
readonly tag: "Link";
readonly first: Free;
readonly second: Free;
};
/**
* @since 2.0.0
*/
export type Free = Node | Link;
/**
* @since 2.0.0
*/
export interface KindFree extends Kind {
readonly kind: Free>;
}
/**
* @since 2.0.0
*/
export function node(value: A): Free {
return { tag: "Node", value };
}
/**
* @since 2.0.0
*/
export function link(
first: Free,
second: Free,
): Free {
return { tag: "Link", first, second };
}
/**
* @since 2.0.0
*/
export function isNode(ua: Free): ua is Node {
return ua.tag === "Node";
}
/**
* @since 2.0.0
*/
export function isLink(ua: Free): ua is Link {
return ua.tag === "Link";
}
/**
* @since 2.0.0
*/
export function match(
onNode: (value: A) => O,
onLink: (first: Free, second: Free) => O,
): (ua: Free) => O {
return (ua) => {
switch (ua.tag) {
case "Node":
return onNode(ua.value);
case "Link":
return onLink(ua.first, ua.second);
}
};
}
/**
* @since 2.0.0
*/
export function combine(
second: Free,
): (first: Free) => Free {
return (first) => ({ tag: "Link", first, second });
}
/**
* @since 2.0.0
*/
export function wrap(a: A): Free {
return node(a);
}
/**
* @since 2.0.0
*/
export function map(
fai: (a: A) => I,
): (ua: Free) => Free {
const go: (ua: Free) => Free = match(
flow(fai, node),
(first, second) => link(go(first), go(second)),
);
return go;
}
/**
* @since 2.0.0
*/
export function flatmap(
faui: (a: A) => Free,
): (ua: Free) => Free {
const go: (ua: Free) => Free = match(
faui,
(first, second): Free => link(go(first), go(second)),
);
return go;
}
/**
* @since 2.0.0
*/
export function apply(ua: Free): (ufai: Free<(a: A) => I>) => Free {
return (ufai) => pipe(ufai, flatmap(flow(map, (fn) => fn(ua))));
}
/**
* @since 2.0.0
*/
export function fold(
foldr: (value: A, accumulator: O) => O,
initial: O,
): (ua: Free) => O {
// :(
let result = initial;
const go: (ua: Free) => O = match(
(value) => {
result = foldr(value, result);
return result;
},
(first, second) => {
go(first);
return go(second);
},
);
return go;
}
/**
* @since 2.0.0
*/
export function getCombinable(): Combinable> {
return { combine };
}
// TODO: Showable, Sortable, Traversable
//