Sibling Communication via Seals, in Scheme
(a form of synergy)
A Pattern:
( ; A clan of code
let* ((clan-seal (new-seal))(cseal (car clan-seal))(cunseal
(cadr clan-seal)))
; ...
; code that produces and exports procedures
; When a call site in this clan invokes a procedure
; that it expects to be from this clan, but cannot
be sure,
; it seals the message with cseal.
; When a procedure produced by this clan is invoked
by what it
; expects to be a call site in the clan, but cannot
be sure,
; it unseals the message with cunseal.
; ...
)
Here is a worked example.
Picture badge pattern
When someone sends you a Scheme value purported to be a member of your clan, how do you recognize a clan member?
Each clan has its own seal.
The sealer is not allowed outside the clan.
Each member carries a picture badge which she supplies on request.
p’s badge is (seal p).
While an impostor may ask p for her badge and abscond with it, it is useless to the impostor.
A clan member, when checking the badge will ask p for her badge, unseal the badge, and compare (eq?) p with the unsealed value.
(eq? (unseal (p "badge please")) p)
Below are descriptions of this Scheme code that illustrates trivial rights amplification.
- ylppa
- is just a convenient hack to evaluate an expression that returns a list, and quickly and efficiently assign names to members of that list.
Depending on the implementation I suspect that it is more efficient than the more obvious ways, as well as being more concise.
ylppa seems better to me than the proposed R6RS ‘call-with-values’ construct.
- new-cell
- creates half of a cons cell.
I think that Rees introduces new-cell for pedagogical reasons.
I use the car of a cons cell here.
- new-seal
- This might be a primitive but here is implemented out of “eq?”.
“(ylppa (new-seal)(lambda (seal unseal valid?) (eq? (unseal (seal x)) x)))” yields “#t”# for all x.
“(ylppa (new-seal)(lambda (seal unseal valid?)
(ylppa (new-seal)(lambda (sealX unsealX validX?) (unseal (sealX x))))))” fails.
The unsealer only unseals what the mated sealer sealed.
- css
- is a source of Spairs.
If (cs . amp) is an Spair then cs is a source of counters.
Each evaluation (cs) produces a new independent counter.
“(let ((cr (cs))) (list (cr) (cr)))” creates a counter cr, pokes it twice yielding '(0 1).
If cr is such a counter from cs and amp is the amplifier that came with cs, then (amp cr) yields a reseter for cr.
(let* ((csp (css))(cs (car csp))(amp (cdr csp))(cr (cs))(cw (amp cr)))
(cw 13)(cr)) yields 13 after cw reset cr’s memory.
This is the essence of amplification; amp amplifies cr to produce cw.
Classes of problems are solved by making cr and perhaps cs available in places where amp is unavailable.
Contingent events may lead to bringing amp together with cr to produce cw; situations arise where the need for reset is rare.
Jonathan Shapiro reminds me that there is a primitive simpler than seal and more efficient than “eq?” from which to build new-seal.
The Keykos domain tool and the Eros process tool provide little more than this.
This is illustrated in the ‘safe’ design.
An annotated transcript:
> (((car (css)))) ; produce pair, then counter source, then counter, then invoke.
0
> (define SP (css)) ; Make specific (counter source . amplifier) pair.
> SP
(# . #)
> (define cs (car SP)) ; extract the source
> (define amp (cdr SP)) ; extract the amplifier
> (define c1 (cs)) ; Make a counter
> (c1) ; Poke the counter
0
> (c1) ; Poke it again
1
> (c1) ; again
2
> (define c2 (cs)) ; Make a new counter
> (c2) ; Poke it — it has an independent value
0
> ((amp c2) 44) ; produce writer for second cell and write 44 into the cell.
> (c2) ; Poke modified counter
44
> (c2) ; Poke it again.
45
Note that this implementation of sealers from eq? is badly inefficient in two ways; both the time cost and space cost are proportional to how many objects have ever been sealed.
Even worse the sealed objects can never be garbage collected.
A newer note