From 481f866adaa26ec9b2dbdf75723e16f2553ae4c2 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 25 Feb 2021 23:16:05 +0100 Subject: [PATCH] Improvements to Typed Records --- implementations/javascript/src/annotated.ts | 4 +- implementations/javascript/src/codec.ts | 3 +- implementations/javascript/src/fold.ts | 8 ++-- implementations/javascript/src/record.ts | 38 +++++++++---------- implementations/javascript/src/values.ts | 8 ++-- implementations/javascript/test/codec.test.ts | 21 +++++----- 6 files changed, 42 insertions(+), 40 deletions(-) diff --git a/implementations/javascript/src/annotated.ts b/implementations/javascript/src/annotated.ts index f924c4e..d854315 100644 --- a/implementations/javascript/src/annotated.ts +++ b/implementations/javascript/src/annotated.ts @@ -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(v: Value, depth: num const nextDepth = depth - 1; function walk(v: Value): Value { return step(v, nextDepth); } - if (Record.isRecord, T>(v.item)) { + if (Record.isRecord, Tuple>, 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[]).map(walk); diff --git a/implementations/javascript/src/codec.ts b/implementations/javascript/src/codec.ts index fdbd9a5..d7be394 100644 --- a/implementations/javascript/src/codec.ts +++ b/implementations/javascript/src/codec.ts @@ -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 { this.encodebytes(Tag.ByteString, bs); } } - else if (Record.isRecord, T>(v)) { + else if (Record.isRecord, Tuple>, T>(v)) { this.emitbyte(Tag.Record); this.push(v.label); for (let i of v) { this.push(i); } diff --git a/implementations/javascript/src/fold.ts b/implementations/javascript/src/fold.ts index b8424a3..012ee74 100644 --- a/implementations/javascript/src/fold.ts +++ b/implementations/javascript/src/fold.ts @@ -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> = (v: Value) => R; @@ -11,7 +11,7 @@ export interface FoldMethods { bytes(b: Bytes): R; symbol(s: symbol): R; - record(r: Record, T>, k: Fold): R; + record(r: Record, Tuple>, T>, k: Fold): R; array(a: Array>, k: Fold): R; set(s: Set, k: Fold): R; dictionary(d: Dictionary, T>, k: Fold): R; @@ -43,7 +43,7 @@ export abstract class ValueFold implemen symbol(s: symbol): Value { return s; } - record(r: Record, T>, k: Fold>): Value { + record(r: Record, Tuple>, T>, k: Fold>): Value { return Record(k(r.label), r.map(k)); } array(a: Value[], k: Fold>): Value { @@ -99,7 +99,7 @@ export function fold(v: Value, o: FoldMethods): R case 'symbol': return o.symbol(v); case 'object': - if (Record.isRecord, T>(v)) { + if (Record.isRecord, Tuple>, T>(v)) { return o.record(v, walk); } else if (Array.isArray(v)) { return o.array(v, walk); diff --git a/implementations/javascript/src/record.ts b/implementations/javascript/src/record.ts index 1628fd3..d266724 100644 --- a/implementations/javascript/src/record.ts +++ b/implementations/javascript/src/record.ts @@ -2,21 +2,21 @@ import { DefaultPointer, is, Value } from "./values"; export type Tuple = Array | [T]; -export type Record, T extends object = DefaultPointer> - = Array> & { label: LabelType }; +export type Record, FieldsType extends Tuple>, T extends object = DefaultPointer> + = FieldsType & { label: LabelType }; -export type RecordGetters, T extends object, Fs> = { - [K in string & keyof Fs]: (r: Record) => Fs[K]; +export type RecordGetters = { + [K in string & keyof Fs]: (r: R) => Fs[K]; }; -export type CtorTypes, T extends object> = +export type CtorTypes> = { [K in keyof Names]: Fs[keyof Fs & Names[K]] } & any[]; export interface RecordConstructor, Fs, Names extends Tuple, T extends object = DefaultPointer> { - (...fields: CtorTypes): Record; + (...fields: CtorTypes): Record, T>; constructorInfo: RecordConstructorInfo; - isClassOf(v: any): v is Record; - _: RecordGetters; + isClassOf(v: any): v is Record, T>; + _: RecordGetters, T>>; }; export interface RecordConstructorInfo, T extends object = DefaultPointer> { @@ -24,15 +24,15 @@ export interface RecordConstructorInfo, T extends object = De arity: number; } -export function Record, T extends object = DefaultPointer>( - label: L, fields: Array>): Record +export function Record, FieldsType extends Tuple>, T extends object = DefaultPointer>( + label: L, fields: FieldsType): Record { (fields as any).label = label; - return fields as Record; + return fields as Record; } export namespace Record { - export function isRecord, T extends object = DefaultPointer>(x: any): x is Record { + export function isRecord, FieldsType extends Tuple>, T extends object = DefaultPointer>(x: any): x is Record { return Array.isArray(x) && 'label' in x; } @@ -40,14 +40,14 @@ export namespace Record { return ''; } - export function constructorInfo, T extends object = DefaultPointer>( - r: Record): RecordConstructorInfo + export function constructorInfo, FieldsType extends Tuple>, T extends object = DefaultPointer>( + r: Record): RecordConstructorInfo { return { label: r.label, arity: r.length }; } - export function isClassOf, T extends object = DefaultPointer>( - ci: RecordConstructorInfo, v: any): v is Record + export function isClassOf, FieldsType extends Tuple>, T extends object = DefaultPointer>( + ci: RecordConstructorInfo, v: any): v is Record { return (Record.isRecord(v)) && is(ci.label, v.label) && (ci.arity === v.length); } @@ -58,13 +58,13 @@ export namespace Record { { return , Names extends Tuple>(label: L, fieldNames: Names) => { const ctor: RecordConstructor = - ((...fields: CtorTypes) => + ((...fields: CtorTypes) => Record(label, fields)) as RecordConstructor; const constructorInfo = { label, arity: fieldNames.length }; ctor.constructorInfo = constructorInfo; - ctor.isClassOf = (v: any): v is Record => Record.isClassOf(constructorInfo, v); + ctor.isClassOf = (v: any): v is Record, T> => Record.isClassOf(constructorInfo, v); (ctor as any)._ = {}; - fieldNames.forEach((name, i) => (ctor._ as any)[name] = (r: Record) => r[i]); + fieldNames.forEach((name, i) => (ctor._ as any)[name] = (r: Record, T>) => r[i]); return ctor; }; } diff --git a/implementations/javascript/src/values.ts b/implementations/javascript/src/values.ts index 333903e..724ac6e 100644 --- a/implementations/javascript/src/values.ts +++ b/implementations/javascript/src/values.ts @@ -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 = Atom | Compound | T | Annotated; export type Atom = boolean | SingleFloat | DoubleFloat | number | string | Bytes | symbol; -export type Compound = Record | Array> | Set | Dictionary, T>; +export type Compound = Record | Array> | Set | Dictionary, T>; export function fromJS(x: any): Value { switch (typeof x) { @@ -44,7 +44,7 @@ export function fromJS(x: any): Value { if (typeof x[AsPreserve] === 'function') { return x[AsPreserve](); } - if (Record.isRecord, T>(x)) { + if (Record.isRecord, Tuple>, 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; + const r = this as Record, DefaultPointer>; return r.label.asPreservesText() + '(' + r.map(f => { try { diff --git a/implementations/javascript/test/codec.test.ts b/implementations/javascript/test/codec.test.ts index 2c0db4f..59d4140 100644 --- a/implementations/javascript/test/codec.test.ts +++ b/implementations/javascript/test/codec.test.ts @@ -198,18 +198,19 @@ describe('common test suite', () => { back: 5 }, annotation5: { forward: annotate( - Record(Symbol.for('R'), - [annotate(Symbol.for('f'), - Symbol.for('af'))]), + Record(Symbol.for('R'), + [annotate(Symbol.for('f'), + Symbol.for('af'))]), Symbol.for('ar')), - back: Record, Pointer>(Symbol.for('R'), [Symbol.for('f')]) + back: Record, any, Pointer>(Symbol.for('R'), [Symbol.for('f')]) }, annotation6: { - forward: Record, Pointer>(annotate(Symbol.for('R'), - Symbol.for('ar')), - [annotate(Symbol.for('f'), - Symbol.for('af'))]), - back: Record(Symbol.for('R'), [Symbol.for('f')]) + forward: Record, any, Pointer>( + annotate(Symbol.for('R'), + Symbol.for('ar')), + [annotate(Symbol.for('f'), + Symbol.for('af'))]), + back: Record(Symbol.for('R'), [Symbol.for('f')]) }, annotation7: { forward: annotate([], 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, Pointer>; tests.forEach((t0: Value, tName0: Value) => { const tName = Symbol.keyFor(strip(tName0) as symbol)!; - const t = peel(t0) as Record; + const t = peel(t0) as Record; switch (t.label) { case Symbol.for('Test'): runTestCase('normal', tName, strip(t[0]) as Bytes, t[1]);