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,
Record,
Value,
fold,
is,
mapPointers,
unannotate,
} 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 Rewrite = { pattern: Pattern, template: Template };
export type PatternPointer =
| Ref
| { type: 'discard' }
| { type: 'bind', name: string, pattern: Pattern }
| { type: 'and', patterns: Array<Pattern> }
| { type: 'not', pattern: Pattern };
export type Pattern = Value<PatternPointer>;
export const _CRec = Symbol.for('rec');
export const CRec = Record.makeConstructor<{label: Assertion, arity: number}, Ref>()(
_CRec, ['label', 'arity']);
export type CRec = ReturnType<typeof CRec>;
export type TemplatePointer = Ref | { type: 'ref', name: string };
export type Template = Value<TemplatePointer>;
export const _CArr = Symbol.for('arr');
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 };
@ -52,14 +101,6 @@ export function isRef(v: any): v is Ref {
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 {
readonly outbound: Map<Handle, Ref>;
exitReason: ExitReason = null;
@ -179,78 +220,110 @@ export function match(p: Pattern, v: Assertion): Bindings | null {
let bindings: Bindings = {};
function walk(p: Pattern, v: Assertion): boolean {
return fold(p, {
boolean(_b) { return is(p, v); },
single(_f) { return is(p, v); },
double(_f) { return is(p, v); },
integer(_i) { return is(p, v); },
string(_s) { return is(p, v); },
bytes(_b) { return is(p, v); },
symbol(_s) { return is(p, v); },
record(r) {
v = unannotate(v);
return Record.isRecord<Ref>(v)
&& r.length === v.length
&& 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;
switch (p.label) {
case _PDiscard:
return true;
case _PBind:
if (walk(PBind._.pattern(p) as Pattern, v)) {
bindings[PBind._.name(p)] = v;
return true;
}
return false;
case _PAnd:
for (const pp of PAnd._.patterns(p) as Pattern[]) {
if (!walk(pp, v)) return false;
}
return true;
},
annotated(a) { return walk(a.item, v); },
pointer(t) {
if ('type' in t) {
switch (t.type) {
case 'bind':
if (walk(t.pattern, v)) {
bindings[t.name] = v;
return true;
}
return false;
case 'discard':
return true;
case 'not': {
const savedBindings = bindings;
bindings = {};
const result = !walk(t.pattern, v)
bindings = savedBindings;
return result;
case _PNot: {
const savedBindings = bindings;
bindings = {};
const result = !walk(PNot._.pattern(p) as Pattern, v)
bindings = savedBindings;
return result;
}
case _Lit:
return is(Lit._.value(p), v);
case _PCompound: {
const ctor = PCompound._.ctor(p);
const members = PCompound._.members(p) as Dictionary<Pattern, Ref>;
switch (ctor.label) {
case _CRec:
if (!Record.isRecord<Assertion, Ref>(v)) return false;
if (!is(CRec._.label(ctor), v.label)) return false;
if (CRec._.arity(ctor) !== v.length) return false;
for (const [key, pp] of members) {
if (typeof key !== 'number') return false;
if (!walk(pp, v[key])) return false;
}
case 'and':
for (const p of t.patterns) {
if (!walk(p, v)) return false;
}
return true;
}
} else {
if (!isRef(v)) return false;
return (t.relay === v.relay) && (t.target === v.target);
return true;
case _CArr:
if (!Array.isArray(v)) return false;
if ('label' in v) return false;
if (CArr._.arity(ctor) !== v.length) return false;
for (const [key, pp] of members) {
if (typeof key !== 'number') return false;
if (!walk(pp, v[key])) return false;
}
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;
}
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 {
@ -279,7 +352,7 @@ export function runRewrites(a: Attenuation | undefined, v: Assertion): Assertion
}
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 {

View File

@ -27,15 +27,16 @@ import { Bag, ChangeDescription } from './bag';
// observer-observers and attenuator patterns don't have to deal with
// 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> {
readonly handleMap: IdentityMap<Handle, Record<Ref>> = new IdentityMap();
readonly handleMap: IdentityMap<Handle, Record<Assertion, Ref>> = new IdentityMap();
readonly assertions = new Bag<Ref>();
readonly subscriptions: Dictionary<Map<Ref, Dictionary<Handle>>> = new Dictionary();
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);
if (this.assertions.change(rec, +1) !== ChangeDescription.ABSENT_TO_PRESENT) return;
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());
this.subscriptions.get(label)!.set(observer, seen);
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)));
}
this.subscriptions.get(rec.label)?.forEach((seen, peer) =>
@ -72,7 +73,7 @@ export class Dataspace implements Partial<Entity> {
}
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));
}
}

29
main.ts
View File

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