forked from syndicate-lang/preserves
Folds
This commit is contained in:
parent
407e8778a1
commit
a69297c3ba
|
@ -25,6 +25,8 @@ export class Dictionary<V, T extends object = DefaultPointer> extends FlexMap<Va
|
|||
return d;
|
||||
}
|
||||
|
||||
constructor(items?: readonly [any, V][]);
|
||||
constructor(items?: Iterable<readonly [any, V]>);
|
||||
constructor(items?: Iterable<readonly [any, V]>) {
|
||||
const iter = items?.[Symbol.iterator]();
|
||||
super(canonicalString, iter === void 0 ? void 0 : _iterMap(iter, ([k,v]) => [fromJS(k), v]));
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
import { Bytes, Value, Record, Set, Dictionary, Single, Double, Annotated, annotate, Float } from "./values";
|
||||
|
||||
export type Fold<T extends object, R = Value<T>> = (v: Value<T>) => R;
|
||||
|
||||
export interface FoldMethods<T extends object, 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<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<Value<T>, T>, k: Fold<T, R>): R;
|
||||
|
||||
annotated(a: Annotated<T>, k: Fold<T, R>): R;
|
||||
|
||||
pointer(t: T, k: Fold<T, R>): R;
|
||||
}
|
||||
|
||||
export abstract class ValueFold<T extends object, R extends object = 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<T>, k: Fold<T, Value<R>>): Value<R> {
|
||||
return new 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<Value<T>, 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 pointer(t: T, k: Fold<T, Value<R>>): Value<R>;
|
||||
}
|
||||
|
||||
export class IdentityFold<T extends object> extends ValueFold<T, T> {
|
||||
pointer(t: T, _k: Fold<T, Value<T>>): Value<T> {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
export class MapFold<T extends object, R extends object> extends ValueFold<T, R> {
|
||||
readonly f: (t: T) => R;
|
||||
|
||||
constructor(f: (t: T) => R) {
|
||||
super();
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
pointer(t: T, _k: Fold<T, Value<R>>): Value<R> {
|
||||
return this.f(t);
|
||||
}
|
||||
}
|
||||
|
||||
export const IDENTITY_FOLD = new IdentityFold();
|
||||
|
||||
export function fold<T extends object, 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<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<Value<T>, 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 {
|
||||
/* fall through */
|
||||
}
|
||||
default:
|
||||
return o.pointer(v, walk);
|
||||
}
|
||||
};
|
||||
return walk(v);
|
||||
}
|
||||
|
||||
export function mapPointers<T extends object, R extends object>(
|
||||
v: Value<T>,
|
||||
f: (t: T) => R,
|
||||
): Value<R>
|
||||
{
|
||||
return fold(v, new MapFold(f));
|
||||
}
|
|
@ -3,4 +3,5 @@ export * from './symbols';
|
|||
export * from './codec';
|
||||
export * from './values';
|
||||
export * from './text';
|
||||
export * from './fold';
|
||||
export * as Constants from './constants';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Single, Double } from '../src/index';
|
||||
import { Single, Double, fromJS, Dictionary, IDENTITY_FOLD, fold, mapPointers, Value } from '../src/index';
|
||||
import './test-utils';
|
||||
|
||||
describe('Single', () => {
|
||||
|
@ -9,6 +9,30 @@ describe('Single', () => {
|
|||
|
||||
describe('Double', () => {
|
||||
it('should print reasonably', () => {
|
||||
expect(Double(123.45).toString()).toEqual("123.45q");
|
||||
expect(Double(123.45).toString()).toEqual("123.45");
|
||||
});
|
||||
});
|
||||
|
||||
describe('fold', () => {
|
||||
function mkv<T extends object>(t: T): Value<T> {
|
||||
return fromJS<T>([
|
||||
1,
|
||||
2,
|
||||
new Dictionary([[[3, 4], fromJS([5, 6])],
|
||||
['a', 1],
|
||||
['b', true]]),
|
||||
Single(3.4),
|
||||
t,
|
||||
]);
|
||||
}
|
||||
|
||||
it('should support identity', () => {
|
||||
const w = new Date();
|
||||
const v = mkv(w);
|
||||
expect(fold(v, IDENTITY_FOLD)).is(v);
|
||||
const w1 = new Date();
|
||||
const v1 = mkv(w1);
|
||||
expect(fold(v, IDENTITY_FOLD)).not.is(v1);
|
||||
expect(mapPointers(v, _t => w1)).is(v1);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue