Lifted patterns and templates; typed records

This commit is contained in:
Tony Garnock-Jones 2021-02-25 22:24:21 +01:00
parent 99e6b6ab3d
commit 3b2e544f35
3 changed files with 183 additions and 96 deletions

239
actor.ts
View File

@ -3,10 +3,7 @@ import {
IdentitySet, IdentitySet,
Record, Record,
Value, Value,
fold,
is, is,
mapPointers,
unannotate,
} from 'preserves'; } from 'preserves';
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -33,16 +30,68 @@ export type Attenuation = Array<RewriteStage>; // array of stages, each a list o
export type RewriteStage = Array<Rewrite>; export type RewriteStage = Array<Rewrite>;
export type Rewrite = { pattern: Pattern, template: Template }; export type Rewrite = { pattern: Pattern, template: Template };
export type PatternPointer = export const _CRec = Symbol.for('rec');
| Ref export const CRec = Record.makeConstructor<{label: Assertion, arity: number}, Ref>()(
| { type: 'discard' } _CRec, ['label', 'arity']);
| { type: 'bind', name: string, pattern: Pattern } export type CRec = ReturnType<typeof CRec>;
| { type: 'and', patterns: Array<Pattern> }
| { type: 'not', pattern: Pattern };
export type Pattern = Value<PatternPointer>;
export type TemplatePointer = Ref | { type: 'ref', name: string }; export const _CArr = Symbol.for('arr');
export type Template = Value<TemplatePointer>; export const CArr = Record.makeConstructor<{arity: number}, Ref>()(
_CArr, ['arity']);
export type CArr = ReturnType<typeof CArr>;
export const _CDict = Symbol.for('dict');
export const CDict = Record.makeConstructor<{}, Ref>()(
_CDict, []);
export type CDict = ReturnType<typeof CDict>;
export type ConstructorSpec = CRec | CArr | CDict;
export const _PDiscard = Symbol.for('_');
export const PDiscard = Record.makeConstructor<{}, Ref>()(
_PDiscard, []);
export type PDiscard = ReturnType<typeof PDiscard>;
export const _PBind = Symbol.for('bind');
export const PBind = Record.makeConstructor<{name: string, pattern: Assertion}, Ref>()(
_PBind, ['name', 'pattern']);
export type PBind = ReturnType<typeof PBind>;
export const _PAnd = Symbol.for('and');
export const PAnd = Record.makeConstructor<{patterns: Array<Assertion>}, Ref>()(
_PAnd, ['patterns']);
export type PAnd = ReturnType<typeof PAnd>;
export const _PNot = Symbol.for('not');
export const PNot = Record.makeConstructor<{pattern: Assertion}, Ref>()(
_PNot, ['pattern']);
export type PNot = ReturnType<typeof PNot>;
export const _Lit = Symbol.for('lit');
export const Lit = Record.makeConstructor<{value: Assertion}, Ref>()(
_Lit, ['value']);
export type Lit = ReturnType<typeof Lit>;
export const _PCompound = Symbol.for('compound');
export const PCompound =
Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary<Assertion>}, Ref>()(
_PCompound, ['ctor', 'members']);
export type PCompound = ReturnType<typeof PCompound>;
export type Pattern = PDiscard | PBind | PAnd | PNot | Lit | PCompound;
export const _TRef = Symbol.for('ref');
export const TRef = Record.makeConstructor<{name: string}, Ref>()(
_TRef, ['name']);
export type TRef = ReturnType<typeof TRef>;
export const _TCompound = Symbol.for('compound');
export const TCompound =
Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary<Assertion>}, Ref>()(
_TCompound, ['ctor', 'members']);
export type TCompound = ReturnType<typeof TCompound>;
export type Template = TRef | Lit | TCompound;
export type Bindings = { [name: string]: Assertion }; export type Bindings = { [name: string]: Assertion };
@ -52,14 +101,6 @@ export function isRef(v: any): v is Ref {
return 'relay' in v && v.relay instanceof Actor && 'target' in v; return 'relay' in v && v.relay instanceof Actor && 'target' in v;
} }
export const pdiscard: Pattern = { type: 'discard' };
export const pbind = (name: string, pattern: Pattern = pdiscard): Pattern =>
({ type: 'bind', name, pattern });
export const pand = (... patterns: Pattern[]): Pattern => ({ type: 'and', patterns });
export const pnot = (pattern: Pattern): Pattern => ({ type: 'not', pattern });
export const tref = (name: string): Template => ({ type: 'ref', name });
export class Actor { export class Actor {
readonly outbound: Map<Handle, Ref>; readonly outbound: Map<Handle, Ref>;
exitReason: ExitReason = null; exitReason: ExitReason = null;
@ -179,78 +220,110 @@ export function match(p: Pattern, v: Assertion): Bindings | null {
let bindings: Bindings = {}; let bindings: Bindings = {};
function walk(p: Pattern, v: Assertion): boolean { function walk(p: Pattern, v: Assertion): boolean {
return fold(p, { switch (p.label) {
boolean(_b) { return is(p, v); }, case _PDiscard:
single(_f) { return is(p, v); }, return true;
double(_f) { return is(p, v); }, case _PBind:
integer(_i) { return is(p, v); }, if (walk(PBind._.pattern(p) as Pattern, v)) {
string(_s) { return is(p, v); }, bindings[PBind._.name(p)] = v;
bytes(_b) { return is(p, v); }, return true;
symbol(_s) { return is(p, v); }, }
record(r) { return false;
v = unannotate(v); case _PAnd:
return Record.isRecord<Ref>(v) for (const pp of PAnd._.patterns(p) as Pattern[]) {
&& r.length === v.length if (!walk(pp, v)) return false;
&& walk(r.label, v.label)
&& r.every((pp, i) => walk(pp, (v as Record<Ref>)[i]));
},
array(a) {
v = unannotate(v);
return Array.isArray(v)
&& a.length === v.length
&& a.every((pp, i) =>
walk(pp, (v as Array<Value<Ref>>)[i]));
},
set(_s) { return false; }, // TODO: set patterns
dictionary(d) {
v = unannotate(v);
if (!Dictionary.isDictionary<Ref>(v)) return false;
if (d.size !== v.size) return false;
for (const pe of d.entries()) {
const [pk, pv] = pe;
if (!v.has(pk)) return false;
if (!walk(pv, v.get(pk)!)) return false;
} }
return true; return true;
}, case _PNot: {
annotated(a) { return walk(a.item, v); }, const savedBindings = bindings;
pointer(t) { bindings = {};
if ('type' in t) { const result = !walk(PNot._.pattern(p) as Pattern, v)
switch (t.type) { bindings = savedBindings;
case 'bind': return result;
if (walk(t.pattern, v)) { }
bindings[t.name] = v; case _Lit:
return true; return is(Lit._.value(p), v);
} case _PCompound: {
return false; const ctor = PCompound._.ctor(p);
case 'discard': const members = PCompound._.members(p) as Dictionary<Pattern, Ref>;
return true; switch (ctor.label) {
case 'not': { case _CRec:
const savedBindings = bindings; if (!Record.isRecord<Assertion, Ref>(v)) return false;
bindings = {}; if (!is(CRec._.label(ctor), v.label)) return false;
const result = !walk(t.pattern, v) if (CRec._.arity(ctor) !== v.length) return false;
bindings = savedBindings; for (const [key, pp] of members) {
return result; if (typeof key !== 'number') return false;
if (!walk(pp, v[key])) return false;
} }
case 'and': return true;
for (const p of t.patterns) { case _CArr:
if (!walk(p, v)) return false; if (!Array.isArray(v)) return false;
} if ('label' in v) return false;
return true; if (CArr._.arity(ctor) !== v.length) return false;
} for (const [key, pp] of members) {
} else { if (typeof key !== 'number') return false;
if (!isRef(v)) return false; if (!walk(pp, v[key])) return false;
return (t.relay === v.relay) && (t.target === v.target); }
return true;
case _CDict:
if (!Dictionary.isDictionary<Assertion, Ref>(v)) return false;
for (const [key, pp] of members) {
const vv = v.get(key);
if (vv === void 0) return false;
if (!walk(pp, vv)) return false;
}
return true;
} }
}, }
}); }
} }
return walk(p, v) ? bindings : null; return walk(p, v) ? bindings : null;
} }
export function instantiate(t: Template, b: Bindings): Assertion { export function instantiate(t: Template, b: Bindings): Assertion {
return mapPointers(t, p => ('type' in p) ? b[p.name] : p); function walk(t: Template): Assertion {
switch (t.label) {
case _TRef: {
const v = b[TRef._.name(t)];
if (v === void 0) throw new Error(`Unbound reference: ${TRef._.name(t)}`);
return v;
}
case _Lit:
return Lit._.value(t);
case _TCompound: {
const ctor = TCompound._.ctor(t);
const members = TCompound._.members(t) as Dictionary<Template, Ref>;
switch (ctor.label) {
case _CRec: {
const v = Record<Assertion, Ref>(CRec._.label(ctor), []);
v.length = CRec._.arity(ctor);
for (const [key, tt] of members) {
v[key as number] = walk(tt);
}
return v;
}
case _CArr: {
const v = [];
v.length = CArr._.arity(ctor);
for (const [key, tt] of members) {
v[key as number] = walk(tt);
}
return v;
}
case _CDict: {
const v = new Dictionary<Assertion, Ref>();
for (const [key, tt] of members) {
v.set(key, walk(tt));
}
return v;
}
}
}
}
}
return walk(t);
} }
export function rewrite(r: Rewrite, v: Assertion): Assertion | null { export function rewrite(r: Rewrite, v: Assertion): Assertion | null {
@ -279,7 +352,7 @@ export function runRewrites(a: Attenuation | undefined, v: Assertion): Assertion
} }
export function rfilter(... patterns: Pattern[]): RewriteStage { export function rfilter(... patterns: Pattern[]): RewriteStage {
return patterns.map(p => ({ pattern: pbind('a', p), template: tref('a') })); return patterns.map(p => ({ pattern: PBind('a', p), template: TRef('a') }));
} }
export function attenuate(ref: Ref, ... a: Attenuation): Ref { export function attenuate(ref: Ref, ... a: Attenuation): Ref {

View File

@ -27,15 +27,16 @@ import { Bag, ChangeDescription } from './bag';
// observer-observers and attenuator patterns don't have to deal with // observer-observers and attenuator patterns don't have to deal with
// spurious variation. // spurious variation.
// //
export const Observe = Record.makeConstructor('Observe', ['label', 'observer']); export const Observe = Record.makeConstructor<{label: Assertion, observer: Ref}, Ref>()(
Symbol.for('Observe'), ['label', 'observer']);
export class Dataspace implements Partial<Entity> { export class Dataspace implements Partial<Entity> {
readonly handleMap: IdentityMap<Handle, Record<Ref>> = new IdentityMap(); readonly handleMap: IdentityMap<Handle, Record<Assertion, Ref>> = new IdentityMap();
readonly assertions = new Bag<Ref>(); readonly assertions = new Bag<Ref>();
readonly subscriptions: Dictionary<Map<Ref, Dictionary<Handle>>> = new Dictionary(); readonly subscriptions: Dictionary<Map<Ref, Dictionary<Handle>>> = new Dictionary();
assert(turn: Turn, rec: Assertion, handle: Handle): void { assert(turn: Turn, rec: Assertion, handle: Handle): void {
if (!Record.isRecord<Ref>(rec)) return; if (!Record.isRecord<Assertion, Ref>(rec)) return;
this.handleMap.set(handle, rec); this.handleMap.set(handle, rec);
if (this.assertions.change(rec, +1) !== ChangeDescription.ABSENT_TO_PRESENT) return; if (this.assertions.change(rec, +1) !== ChangeDescription.ABSENT_TO_PRESENT) return;
if (Observe.isClassOf(rec)) { if (Observe.isClassOf(rec)) {
@ -45,7 +46,7 @@ export class Dataspace implements Partial<Entity> {
if (!this.subscriptions.has(label)) this.subscriptions.set(label, new Map()); if (!this.subscriptions.has(label)) this.subscriptions.set(label, new Map());
this.subscriptions.get(label)!.set(observer, seen); this.subscriptions.get(label)!.set(observer, seen);
this.assertions.forEach((_count, prev) => this.assertions.forEach((_count, prev) =>
is((prev as Record<Ref>).label, label) is((prev as Record<Assertion, Ref>).label, label)
&& seen.set(prev, turn.assert(observer, prev))); && seen.set(prev, turn.assert(observer, prev)));
} }
this.subscriptions.get(rec.label)?.forEach((seen, peer) => this.subscriptions.get(rec.label)?.forEach((seen, peer) =>
@ -72,7 +73,7 @@ export class Dataspace implements Partial<Entity> {
} }
message(turn: Turn, rec: Assertion): void { message(turn: Turn, rec: Assertion): void {
if (!Record.isRecord<Ref>(rec)) return; if (!Record.isRecord<Assertion, Ref>(rec)) return;
this.subscriptions.get(rec.label)?.forEach((_seen, peer) => turn.message(peer, rec)); this.subscriptions.get(rec.label)?.forEach((_seen, peer) => turn.message(peer, rec));
} }
} }

29
main.ts
View File

@ -5,14 +5,19 @@ import {
Ref, Ref,
Turn, Turn,
attenuate, attenuate,
pdiscard,
rfilter, rfilter,
PDiscard,
PCompound,
CRec,
Lit,
} from './actor.js'; } from './actor.js';
import { Record } from 'preserves'; import { Dictionary, Record } from 'preserves';
import { Dataspace, Observe } from './dataspace.js'; import { Dataspace, Observe } from './dataspace.js';
const BoxState = Record.makeConstructor('BoxState', ['value']); const BoxState = Record.makeConstructor<{value: number}, Ref>()(
const SetBox = Record.makeConstructor('SetBox', ['newValue']); Symbol.for('BoxState'), ['value']);
const SetBox = Record.makeConstructor<{newValue: number}, Ref>()(
Symbol.for('SetBox'), ['newValue']);
let startTime = Date.now(); let startTime = Date.now();
let prevValue = 0; let prevValue = 0;
@ -76,11 +81,19 @@ Turn.for(new Actor(), async (t: Turn) => {
spawnBox(t, attenuate( spawnBox(t, attenuate(
ds, ds,
rfilter(BoxState(pdiscard), rfilter(PCompound(CRec(BoxState.constructorInfo.label,
Observe(SetBox.constructorInfo.label, pdiscard)))); BoxState.constructorInfo.arity),
new Dictionary()),
PCompound(CRec(Observe.constructorInfo.label,
Observe.constructorInfo.arity),
new Dictionary([[0, Lit(SetBox.constructorInfo.label)]])))));
spawnClient(t, attenuate( spawnClient(t, attenuate(
ds, ds,
rfilter(SetBox(pdiscard), rfilter(PCompound(CRec(SetBox.constructorInfo.label,
Observe(BoxState.constructorInfo.label, pdiscard)))); SetBox.constructorInfo.arity),
new Dictionary()),
PCompound(CRec(Observe.constructorInfo.label,
Observe.constructorInfo.arity),
new Dictionary([[0, Lit(BoxState.constructorInfo.label)]])))));
}); });