preserves/implementations/javascript/packages/core/src/fold.ts

141 lines
4.2 KiB
TypeScript

import { Record, Tuple } from "./record";
import { Bytes } from "./bytes";
import { Value } from "./values";
import { Set, Dictionary } from "./dictionary";
import { annotate, Annotated } from "./annotated";
import { Double, Float, Single } from "./float";
import { Embedded } from "./embedded";
export type Fold<T, R = Value<T>> = (v: Value<T>) => R;
export interface FoldMethods<T, R> {
boolean(b: boolean): R;
single(f: number): R;
double(f: number): R;
integer(i: number): R;
string(s: string): R;
bytes(b: Bytes): R;
symbol(s: symbol): R;
record(r: Record<Value<T>, Tuple<Value<T>>, T>, k: Fold<T, R>): R;
array(a: Array<Value<T>>, k: Fold<T, R>): R;
set(s: Set<T>, k: Fold<T, R>): R;
dictionary(d: Dictionary<T>, k: Fold<T, R>): R;
annotated(a: Annotated<T>, k: Fold<T, R>): R;
embedded(t: Embedded<T>, k: Fold<T, R>): R;
}
export abstract class ValueFold<T, R = T> implements FoldMethods<T, Value<R>> {
boolean(b: boolean): Value<R> {
return b;
}
single(f: number): Value<R> {
return Single(f);
}
double(f: number): Value<R> {
return Double(f);
}
integer(i: number): Value<R> {
return i;
}
string(s: string): Value<R> {
return s;
}
bytes(b: Bytes): Value<R> {
return b;
}
symbol(s: symbol): Value<R> {
return s;
}
record(r: Record<Value<T>, Tuple<Value<T>>, T>, k: Fold<T, Value<R>>): Value<R> {
return Record(k(r.label), r.map(k));
}
array(a: Value<T>[], k: Fold<T, Value<R>>): Value<R> {
return a.map(k);
}
set(s: Set<T>, k: Fold<T, Value<R>>): Value<R> {
return s.map(k);
}
dictionary(d: Dictionary<T>, k: Fold<T, Value<R>>): Value<R> {
return d.mapEntries(([key, value]) => [k(key), k(value)]);
}
annotated(a: Annotated<T>, k: Fold<T, Value<R>>): Value<R> {
return annotate(k(a.item), ...a.annotations.map(k));
}
abstract embedded(t: Embedded<T>, k: Fold<T, Value<R>>): Value<R>;
}
export class IdentityFold<T> extends ValueFold<T, T> {
embedded(t: Embedded<T>, _k: Fold<T, Value<T>>): Value<T> {
return t;
}
}
export class MapFold<T, R> extends ValueFold<T, R> {
readonly f: (t: T) => Value<R>;
constructor(f: (t: T) => Value<R>) {
super();
this.f = f;
}
embedded(t: Embedded<T>, _k: Fold<T, Value<R>>): Value<R> {
return this.f(t.embeddedValue);
}
}
export const IDENTITY_FOLD = new IdentityFold<any>();
export function fold<T, R>(v: Value<T>, o: FoldMethods<T, R>): R {
const walk = (v: Value<T>): R => {
switch (typeof v) {
case 'boolean':
return o.boolean(v);
case 'number':
if (!Number.isInteger(v)) {
// TODO: Is this convenience warranted?
return o.double(v);
} else {
return o.integer(v);
}
case 'string':
return o.string(v);
case 'symbol':
return o.symbol(v);
case 'object':
if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(v)) {
return o.record(v, walk);
} else if (Array.isArray(v)) {
return o.array(v, walk);
} else if (Set.isSet<T>(v)) {
return o.set(v, walk);
} else if (Dictionary.isDictionary<T>(v)) {
return o.dictionary(v, walk);
} else if (Annotated.isAnnotated<T>(v)) {
return o.annotated(v, walk);
} else if (Bytes.isBytes(v)) {
return o.bytes(v);
} else if (Float.isSingle(v)) {
return o.single(v.value);
} else if (Float.isDouble(v)) {
return o.double(v.value);
} else {
return o.embedded(v, walk);
}
default:
((_v: never): never => { throw new Error("Internal error"); })(v);
}
};
return walk(v);
}
export function mapEmbeddeds<T, R>(
v: Value<T>,
f: (t: T) => Value<R>,
): Value<R>
{
return fold(v, new MapFold(f));
}