Improvements to Typed Records

This commit is contained in:
Tony Garnock-Jones 2021-02-25 23:16:05 +01:00
parent 993689356b
commit 481f866ada
6 changed files with 42 additions and 40 deletions

View File

@ -2,7 +2,7 @@ import { Encoder } from "./codec";
import { Tag } from "./constants";
import { AsPreserve, PreserveOn } from "./symbols";
import { DefaultPointer, is, Value } from "./values";
import { Record } from './record';
import { Record, Tuple } from './record';
import { Dictionary, Set } from './dictionary';
export const IsPreservesAnnotated = Symbol.for('IsPreservesAnnotated');
@ -72,7 +72,7 @@ export function strip<T extends object = DefaultPointer>(v: Value<T>, depth: num
const nextDepth = depth - 1;
function walk(v: Value<T>): Value<T> { return step(v, nextDepth); }
if (Record.isRecord<Value<T>, T>(v.item)) {
if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(v.item)) {
return Record(step(v.item.label, depth), v.item.map(walk));
} else if (Array.isArray(v.item)) {
return (v.item as Value<T>[]).map(walk);

View File

@ -6,6 +6,7 @@ import {
Dictionary, Set, Bytes, Record, SingleFloat, DoubleFloat,
BytesLike,
Value,
Tuple,
} from './values';
import { Tag } from './constants';
@ -379,7 +380,7 @@ export class Encoder<T extends object> {
this.encodebytes(Tag.ByteString, bs);
}
}
else if (Record.isRecord<Value<T>, T>(v)) {
else if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(v)) {
this.emitbyte(Tag.Record);
this.push(v.label);
for (let i of v) { this.push(i); }

View File

@ -1,4 +1,4 @@
import { Bytes, Value, Record, Set, Dictionary, Single, Double, Annotated, annotate, Float } from "./values";
import { Bytes, Value, Record, Set, Dictionary, Single, Double, Annotated, annotate, Float, Tuple } from "./values";
export type Fold<T extends object, R = Value<T>> = (v: Value<T>) => R;
@ -11,7 +11,7 @@ export interface FoldMethods<T extends object, R> {
bytes(b: Bytes): R;
symbol(s: symbol): R;
record(r: Record<Value<T>, T>, k: Fold<T, R>): 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<Value<T>, T>, k: Fold<T, R>): R;
@ -43,7 +43,7 @@ export abstract class ValueFold<T extends object, R extends object = T> implemen
symbol(s: symbol): Value<R> {
return s;
}
record(r: Record<Value<T>, T>, k: Fold<T, Value<R>>): Value<R> {
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> {
@ -99,7 +99,7 @@ export function fold<T extends object, R>(v: Value<T>, o: FoldMethods<T, R>): R
case 'symbol':
return o.symbol(v);
case 'object':
if (Record.isRecord<Value<T>, T>(v)) {
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);

View File

@ -2,21 +2,21 @@ import { DefaultPointer, is, Value } from "./values";
export type Tuple<T> = Array<T> | [T];
export type Record<LabelType extends Value<T>, T extends object = DefaultPointer>
= Array<Value<T>> & { label: LabelType };
export type Record<LabelType extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends object = DefaultPointer>
= FieldsType & { label: LabelType };
export type RecordGetters<L extends Value<T>, T extends object, Fs> = {
[K in string & keyof Fs]: (r: Record<L, T>) => Fs[K];
export type RecordGetters<Fs, R> = {
[K in string & keyof Fs]: (r: R) => Fs[K];
};
export type CtorTypes<Fs, Names extends Tuple<keyof Fs>, T extends object> =
export type CtorTypes<Fs, Names extends Tuple<keyof Fs>> =
{ [K in keyof Names]: Fs[keyof Fs & Names[K]] } & any[];
export interface RecordConstructor<L extends Value<T>, Fs, Names extends Tuple<keyof Fs>, T extends object = DefaultPointer> {
(...fields: CtorTypes<Fs, Names, T>): Record<L, T>;
(...fields: CtorTypes<Fs, Names>): Record<L, CtorTypes<Fs, Names>, T>;
constructorInfo: RecordConstructorInfo<L, T>;
isClassOf(v: any): v is Record<L, T>;
_: RecordGetters<L, T, Fs>;
isClassOf(v: any): v is Record<L, CtorTypes<Fs, Names>, T>;
_: RecordGetters<Fs, Record<L, CtorTypes<Fs, Names>, T>>;
};
export interface RecordConstructorInfo<L extends Value<T>, T extends object = DefaultPointer> {
@ -24,15 +24,15 @@ export interface RecordConstructorInfo<L extends Value<T>, T extends object = De
arity: number;
}
export function Record<L extends Value<T>, T extends object = DefaultPointer>(
label: L, fields: Array<Value<T>>): Record<L, T>
export function Record<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends object = DefaultPointer>(
label: L, fields: FieldsType): Record<L, FieldsType, T>
{
(fields as any).label = label;
return fields as Record<L, T>;
return fields as Record<L, FieldsType, T>;
}
export namespace Record {
export function isRecord<L extends Value<T>, T extends object = DefaultPointer>(x: any): x is Record<L, T> {
export function isRecord<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends object = DefaultPointer>(x: any): x is Record<L, FieldsType, T> {
return Array.isArray(x) && 'label' in x;
}
@ -40,14 +40,14 @@ export namespace Record {
return '<unprintable_preserves_field_value>';
}
export function constructorInfo<L extends Value<T>, T extends object = DefaultPointer>(
r: Record<L, T>): RecordConstructorInfo<L, T>
export function constructorInfo<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends object = DefaultPointer>(
r: Record<L, FieldsType, T>): RecordConstructorInfo<L, T>
{
return { label: r.label, arity: r.length };
}
export function isClassOf<L extends Value<T>, T extends object = DefaultPointer>(
ci: RecordConstructorInfo<L, T>, v: any): v is Record<L, T>
export function isClassOf<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends object = DefaultPointer>(
ci: RecordConstructorInfo<L, T>, v: any): v is Record<L, FieldsType, T>
{
return (Record.isRecord(v)) && is(ci.label, v.label) && (ci.arity === v.length);
}
@ -58,13 +58,13 @@ export namespace Record {
{
return <L extends Value<T>, Names extends Tuple<keyof Fs>>(label: L, fieldNames: Names) => {
const ctor: RecordConstructor<L, Fs, Names, T> =
((...fields: CtorTypes<Fs, Names, T>) =>
((...fields: CtorTypes<Fs, Names>) =>
Record(label, fields)) as RecordConstructor<L, Fs, Names, T>;
const constructorInfo = { label, arity: fieldNames.length };
ctor.constructorInfo = constructorInfo;
ctor.isClassOf = (v: any): v is Record<L, T> => Record.isClassOf(constructorInfo, v);
ctor.isClassOf = (v: any): v is Record<L, CtorTypes<Fs, Names>, T> => Record.isClassOf(constructorInfo, v);
(ctor as any)._ = {};
fieldNames.forEach((name, i) => (ctor._ as any)[name] = (r: Record<L, T>) => r[i]);
fieldNames.forEach((name, i) => (ctor._ as any)[name] = (r: Record<L, CtorTypes<Fs, Names>, T>) => r[i]);
return ctor;
};
}

View File

@ -3,7 +3,7 @@
import { AsPreserve } from './symbols';
import { Bytes } from './bytes';
import { DoubleFloat, SingleFloat } from './float';
import { Record } from './record';
import { Record, Tuple } from './record';
import { Annotated } from './annotated';
import { Set, Dictionary } from './dictionary';
@ -17,7 +17,7 @@ export type DefaultPointer = object;
export type Value<T extends object = DefaultPointer> = Atom | Compound<T> | T | Annotated<T>;
export type Atom = boolean | SingleFloat | DoubleFloat | number | string | Bytes | symbol;
export type Compound<T extends object = DefaultPointer> = Record<any, T> | Array<Value<T>> | Set<T> | Dictionary<Value<T>, T>;
export type Compound<T extends object = DefaultPointer> = Record<any, any, T> | Array<Value<T>> | Set<T> | Dictionary<Value<T>, T>;
export function fromJS<T extends object = DefaultPointer>(x: any): Value<T> {
switch (typeof x) {
@ -44,7 +44,7 @@ export function fromJS<T extends object = DefaultPointer>(x: any): Value<T> {
if (typeof x[AsPreserve] === 'function') {
return x[AsPreserve]();
}
if (Record.isRecord<Value<T>, T>(x)) {
if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(x)) {
return x;
}
if (Array.isArray(x)) {
@ -112,7 +112,7 @@ Symbol.prototype.asPreservesText = function (): string {
Array.prototype.asPreservesText = function (): string {
if ('label' in (this as any)) {
const r = this as Record<any, any>;
const r = this as Record<Value, Tuple<Value>, DefaultPointer>;
return r.label.asPreservesText() +
'(' + r.map(f => {
try {

View File

@ -198,18 +198,19 @@ describe('common test suite', () => {
back: 5 },
annotation5: {
forward: annotate<Pointer>(
Record<symbol, Pointer>(Symbol.for('R'),
[annotate<Pointer>(Symbol.for('f'),
Symbol.for('af'))]),
Record<symbol, any, Pointer>(Symbol.for('R'),
[annotate<Pointer>(Symbol.for('f'),
Symbol.for('af'))]),
Symbol.for('ar')),
back: Record<Value<Pointer>, Pointer>(Symbol.for('R'), [Symbol.for('f')])
back: Record<Value<Pointer>, any, Pointer>(Symbol.for('R'), [Symbol.for('f')])
},
annotation6: {
forward: Record<Value<Pointer>, Pointer>(annotate<Pointer>(Symbol.for('R'),
Symbol.for('ar')),
[annotate<Pointer>(Symbol.for('f'),
Symbol.for('af'))]),
back: Record<symbol, Pointer>(Symbol.for('R'), [Symbol.for('f')])
forward: Record<Value<Pointer>, any, Pointer>(
annotate<Pointer>(Symbol.for('R'),
Symbol.for('ar')),
[annotate<Pointer>(Symbol.for('f'),
Symbol.for('af'))]),
back: Record<symbol, any, Pointer>(Symbol.for('R'), [Symbol.for('f')])
},
annotation7: {
forward: annotate<Pointer>([], Symbol.for('a'), Symbol.for('b'), Symbol.for('c')),
@ -259,7 +260,7 @@ describe('common test suite', () => {
const tests = peel(TestCases._.cases(peel(samples) as TestCases)) as Dictionary<Value<Pointer>, Pointer>;
tests.forEach((t0: Value<Pointer>, tName0: Value<Pointer>) => {
const tName = Symbol.keyFor(strip(tName0) as symbol)!;
const t = peel(t0) as Record<symbol, Pointer>;
const t = peel(t0) as Record<symbol, any, Pointer>;
switch (t.label) {
case Symbol.for('Test'):
runTestCase('normal', tName, strip(t[0]) as Bytes, t[1]);