3.11 Constructing Graphs: shared (original) (raw)

3.11 Constructing Graphs: shared🔗

(shared ([id expr] ...) body ...+)

Binds ids with shared structure according to exprs and then evaluates the bodys, returning the result of the last expression.

The shared form is similar to letrec, except that special forms of expr are recognized (after partial macro expansion) to construct graph-structured data, where the correspondingletrec would instead produce a use-before-initialization error.

Each expr (after partial expansion) is matched against the following shared-expr grammar, where earlier variants in a production take precedence over later variants:

shared-expr = shell-expr
| plain-expr
shell-expr = (cons in-immutable-expr in-immutable-expr)
| (list in-immutable-expr ...)
| (list* in-immutable-expr ...)
| (append early-expr ... in-immutable-expr)
| (vector-immutable in-immutable-expr ...)
| (box-immutable in-immutable-expr)
| (mcons patchable-expr patchable-expr)
| (vector patchable-expr ...)
| (box patchable-expr)
| (prefix:make-id patchable-expr ...)
in-immutable-expr = shell-id
| shell-expr
| early-expr
shell-id = id
patchable-expr = expr
early-expr = expr
plain-expr = expr

The prefix:make-id identifier above matches three kinds of references. The first kind is any binding whose name has make- in the middle, and where prefix:id has a transformer binding to structure information with a full set of mutator bindings; seeStructure Type Transformer Binding. The second kind is an identifier that itself has atransformer binding to structure information. The third kind is an identifier that has a 'constructor-for syntax propertywhose value is an identifier with a transformer binding to structure information. A shell-id, meanwhile, must be one of theids bound by the shared form to ashell-expr.

When the exprs of the shared form are parsed asshared-expr (taking into account the order of the variants for parsing precedence), the sub-expressions that were parsed viaearly-expr will be evaluated first when the sharedform is evaluated. Among such expressions, they are evaluated in the order as they appear within the shared form. However, any reference to an id bound by shared produces a use-before-initialization errror, even if the binding for the id appears before the corresponding early-expr within theshared form.

The shell-ids and shell-exprs (not countingpatchable-expr and early-expr sub-expressions) are effectively evaluated next:

Next, the plain-exprs are evaluated as for letrec, where a reference to an id raises exn:fail:contract:variable if it is evaluated before the right-hand side of the id binding.

Finally, the patchable-exprs are evaluated and their values replace undefineds in the results ofshell-exprs. At this point, all ids are bound, sopatchable-exprs can create data cycles (but only with cycles that can be created via mutation).

Examples:

> (shared ([a (cons 1 a)]) a)
#0='(1 . #0#)
#0='(1 2 . #0#)
> (shared ([a (cons 1 b)] [b 7]) a)
'(1 . 7)
> (shared ([a a]) ; no indirection... a)
a: undefined;
cannot use before initialization
> (shared ([a (cons 1 b)] ; b is early... [b a]) a)
a: undefined;
cannot use before initialization
> (shared ([a (mcons 1 b)] ; b is patchable... [b a]) a)
#0=(mcons 1 #0#)
'#(#&5 #&5 #&5)
#0='#(#0# #)