Interconfection
Interconfection is a library for building extensible systems, especially module systems. Interconfection extensions cooperate using a kind of quasi-deterministic concurrency, reflecting the reality of a cultural context where authors have developed and published their extensions without staying in perfect lockstep with each other’s work. Interconfection’s concurrency is an expressive solution to module system design concerns having to do with closed-world and open-world extensibility, including the Expression Problem.
Since the extensions of an Interconfection system are considered to be separate processes that communicate, there is a sense in which Interconfection is all about side effects. However, Interconfection’s side effects and Racket’s side effects work at cross purposes. Careless use of Racket side effects can break some of the encapsulation Interconfection relies upon to enforce determinism. Hence, Interconfection is most useful for Racket programs written in a certain purely functional style. Interconfection’s own side effects are expressed using techniques like monads so that they play well with that pure functional style.
1 Interconfection concepts
1.1 Quasi-determinism
Quasi-determinism is a notion explored in "Freeze After Writing: Quasi-Deterministic Parallel Programming with LVars." No two runs of the same quasi-deterministic computation return different values, but some runs are allowed not to return a value because they encounter synchronization errors, out-of-memory conditions, nontermination, or other detours.
For many realistic systems, quasi-determinism is semantically justified by the fact that the program can’t determine for itself whether the user will pull the plug on it, and there’s nothing it can do in that situation to return the correct result.
For the purposes of Interconfection, we actually use a somewhat stricter form of quasi-determinism: Yes our processes can shirk determinism if they encounter errors or resource exhaustion, but we still consider the fact that an error occurred to be part of the program’s result. That is, if there are sufficient resources to run an Interconfection program and at least one error occurs, then at least one error is bound to occur no matter how many times we try to run it. (It’s possible for it to be a different set of errors each time.)
Because of this slightly stronger determinism, Interconfection can’t make much use of the LVar approach’s "freeze after writing" technique. Using that technique, one process freezes a state resource so that later writes to that resource cause errors. In Interconfection, such a "freeze" can’t occur until after we manage to guarentee there will be no more write attempts, at which point it doesn’t accomplish much anyway.
Nevertheless, a similar effect can be achieved in Interconfection using a subsystem that’s been designed for closed-world extensibility. Using a system of "tickets," which essentially maintain a reference count to the shared resource, it’s possible to detect when there are no tickets remaining, at which point the complete set of writes is known and can be passed along to further computations that need to use it.
1.2 Order-invariance
The topic of concurrency might call to mind race conditions, but there are no race conditions as long as the order of effects doesn’t matter. When those effects accumulate contributions to a set, then it’s important that the computation that acts on that complete set can’t detect what order the contributions were made in.
For that reason, many of the exports of Interconfection are operations we call clines, dexes, merges, and fuses, specifically designed for order-invariant manipulation of sets of values.
1.3 Purity
Unfortunately, the arbitrary use of Racket side effects can record the order that the Racket code runs in, which can break the order-invariance of Interconfection’s abstractions and thereby create true race conditions. Interconfection is designed for use in a more purely functional Racket programming style.
There are certain operations we’re committed to calling "pure," in particular raise, lambda, and #%app (the function call operation). This is true even though lambda and #%app can risk out-of-memory errors and nontermination, even though a lambda expression can allocate a new value each time it’s called, and even though raise and #%app (with the wrong arity) can cause run time errors.
Generally speaking, if a Racket operation obeys Interconfection’s notion of quasi-determinism, has no external or internal side effects, and cannot be used to detect an impurity in raise, lambda, or #%app, then we consider it pure.
Of course, we consider operations like set!, parameterize, and the invocation of first-class continuations to be impure because they break referential transparency: They make it so the results of two identical expressions in the same lexical scope can be two completely different values. And we consider calls to procedures like display to be impure even though they always have the same return value, since having two identical display expressions in the same lexical scope has a different effect than having just one and reusing its result.
For Interconfection’s purposes, we consider certain things like eq?, and anything like equal? that depends on them, to be impure operations. That’s because they could otherwise detect an impurity in lambda. By taking this point of view, many other operations can be considered pure, like list and append. An impure program can distinguish the results of (list 1 2) and (list 1 2) using eq?, but a pure program finds them to be indistinguishable.
We consider it impure to catch an exception without (either exhausting resources or) raising an exception to replace it. By catching exceptions, a Racket program can detect which of two subcomputations was attempted first, which directly defeats the order invariance Interconfection’s abstractions establish and depend on for their quasi-determinism.
There may be a few more subtleties than this.
There are cases where a Racket program may have to resort to impurity at the edges even if it makes use of Interconfection in regions of relative purity. For instance, the struct operation itself is impure since it generates a new structure type identity each time it’s used, but Racket programs don’t have a lot of other options for coining user-defined, encapsulated data types. Similarly, quite a number of essential operations in Racket’s macro system are impure. As long as the functions passed to Interconfection operations are pure, these other impure techniques should be fine.
2 Order
(require interconfection/order/base) | |
package: interconfection-lib |
A cline is based on a total ordering on values in its domain, or in other words a binary relation that is reflexive, transitive, and antisymmetric. Its antisymmetry is as fine-grained as possible: If any two values in a cline’s domain are related by that cline in both directions, only impure code will be able to distinguish the two values.
However, a cline does not merely expose this total ordering. Within the cline’s domain, there may be equivalence classes of values for which every two nonequal values will not have their relative order exposed to pure code. When pure code uses getfx-compare-by-cline to compare two values by a cline, it can get several results:
(nothing): The values are not both in the domain.
(just (ordering-lt)): The first value candidly precedes the second.
(just (ordering-eq)): The first value is equal to the second.
(just (ordering-private)): The two values are not equal, and one of them secretly precedes the other, but they fall into the same equivalence class.
(just (ordering-gt)): The first value candidly follows the second.
A dex is like a cline, but it never results in the “candidly precedes” and “candidly follows” cases. Thus, a dex is useful as a kind of equality test.
All the exports of interconfection/order/base are also exported by interconfection/order.
2.1 Orderings
syntax
syntax
match expander
procedure
(ordering-lt? v) → boolean?
v : any/c
For the purposes of impure Racket code, every two ordering-lt values are equal?.
syntax
syntax
match expander
procedure
(ordering-eq? v) → boolean?
v : any/c
For the purposes of impure Racket code, every two ordering-eq values are equal?.
syntax
syntax
match expander
procedure
(ordering-private? v) → boolean?
v : any/c
For the purposes of impure Racket code, every two ordering-private values are equal?.
syntax
syntax
match expander
procedure
(ordering-gt? v) → boolean?
v : any/c
For the purposes of impure Racket code, every two ordering-gt values are equal?.
procedure
(dex-result? x) → boolean?
x : any/c
procedure
(cline-result? x) → boolean?
x : any/c
2.2 Names, Dexes, and Dexed Values
procedure
(getfx-is-in-dex dex x) → (getfx/c boolean?)
dex : dex? x : any/c
Whether this getfx? computation can be run through pure-run-getfx without problems depends on the given dex. This is one way to "call" a dex.
Whether this getfx? computation can be run through pure-run-getfx without problems depends on the given dex. This is one way to "call" a dex.
Whether this getfx? computation can be run through pure-run-getfx without problems depends on the given dex. This is one way to "call" a dex.
procedure
(getfx-compare-by-dex dex a b) → (getfx/c (maybe/c dex-result?))
dex : dex? a : any/c b : any/c
Whether this getfx? computation can be run through pure-run-getfx without problems depends on the given dex. This is one way to "call" a dex.
procedure
(dexed-first-order/c c) → contract?
c : contract?
This is nearly the same as (dexed/c (contract-first-order c)), but its contract-name is based on that of c.
procedure
(dexed-get-dex d) → dex?
d : dexed?
A call to the resulting dex can be run through pure-run-getfx without problems.
When compared by (dex-dex), all dexed-get-dex results are ordering-eq if the corresponding dexed-get-value results are.
procedure
(dexed-get-name d) → name?
d : dexed?
procedure
(dexed-get-value d) → any/c
d : dexed?
A call to this dex can be run through pure-run-getfx without problems.
All presently existing dexes allow this comparison to be fine-grained enough that it trivializes their equational theory. For instance, (dex-default (dex-give-up) (dex-give-up)) and (dex-give-up) can be distinguished this way despite otherwise having equivalent behavior.
A call to this dex can be run through pure-run-getfx without problems.
A call to this dex can be run through pure-run-getfx without problems.
procedure
(dex-give-up) → dex?
A call to this dex can be run through pure-run-getfx without problems.
procedure
(dex-default dex-for-trying-first dex-for-trying-second) → dex? dex-for-trying-first : dex? dex-for-trying-second : dex?
For the sake of nontermination, error, and performance concerns, this attempts to compute the result using dex-for-trying-first before it moves on to dex-for-trying-second.
The invocation of dex-for-trying-second is a tail call.
If calls to the given dexes can be run through pure-run-getfx without problems, then so can a call to this dex.
When compared by (dex-dex), all dex-default values are ordering-eq if their dex-for-trying-first values are and their dex-for-trying-second values are.
procedure
(dex-opaque name dex) → dex?
name : name? dex : dex?
If calls to the given dex can be run through pure-run-getfx without problems, then so can a call to this dex.
When compared by (dex-dex), all dex-opaque values are ordering-eq if their name values are and their dex values are.
procedure
(dex-by-own-method dexed-getfx-get-method) → dex?
dexed-getfx-get-method : (dexed-first-order/c (-> any/c (getfx/c (maybe/c dex?))))
It invokes the getfx? operation with each value. If any of these invocations results in (nothing), those values are not considered to be in this dex’s domain, and the overall result is (nothing). Otherwise, the computation proceeds:
It checks that the dex methods obtained this way are all ordering-eq. If they’re not, the values are evidently distinguishable, and the overall result is (just (ordering-private)). Otherwise, the computation proceeds:
It tail-calls the method.
If the getfx? computations that result from dexed-getfx-get-method and the calls to their resulting dexes can be run through pure-run-getfx without problems, then so can a call to this dex.
When compared by (dex-dex), all dex-by-own-method values are ordering-eq if their dexed-getfx-get-method values are.
procedure
dexed-getfx-unwrap : (dexed-first-order/c (-> dex? (getfx/c dex?)))
If the getfx? computations that result from dexed-getfx-unwrap and the calls to their resulting dexes can be run through pure-run-getfx without problems, then so can a call to this dex.
When compared by (dex-dex), all dex-fix values are ordering-eq if their dexed-getfx-unwrap values are.
syntax
(dex-tuple-by-field-position tupler-expr [field-position-nat dex-expr] ...)
tupler-expr : (tupler/c (=/c (length '(dex-expr ...))))
dex-expr : dex?
Each field-position-nat must be a distinct number indicating which field should be checked by the associated dex, and there must be an entry for every field.
For the sake of nontermination, error, and performance concerns, this dex computes by attempting the given dexes in the order they appear in this call. If a dex before the last one determines a non-ordering-eq result, the following dexes are only checked to be sure their domains contain the respective field values. Otherwise, the last dex, if any, is attempted as a tail call.
If calls to the given dexes can be run through pure-run-getfx without problems, then so can a call to this dex.
When compared by (dex-dex), all dex-tuple-by-field-position values are ordering-eq if they’re for the same tupler, if they have field-position-nat values in the same sequence, and if their dex-expr values are ordering-eq.
syntax
(dex-tuple tupler-expr dex-expr ...)
tupler-expr : (tupler/c (=/c (length '(dex-expr ...))))
dex-expr : dex?
For the sake of nontermination, error, and performance concerns, this dex computes by attempting the given dexes in the order they appear in this call. The last dex, if any, is attempted as a tail call.
If calls to the given dexes can be run through pure-run-getfx without problems, then so can a call to this dex.
When compared by (dex-dex), each dex-tuple value is ordering-eq to the equivalent dex-tuple-by-field-position value.
2.3 Clines
procedure
(get-dex-from-cline cline) → dex?
cline : cline?
If calls to the given cline can be run through pure-run-getfx without problems, then so can a call to the resulting dex.
procedure
(getfx-is-in-cline cline x) → (getfx/c boolean?)
cline : cline? x : any/c
Whether this getfx? computation can be run through pure-run-getfx without problems depends on the given cline. This is one way to "call" a cline.
procedure
(getfx-compare-by-cline cline a b)
→ (getfx/c (maybe/c cline-result?)) cline : cline? a : any/c b : any/c
Whether this getfx? computation can be run through pure-run-getfx without problems depends on the given cline. This is one way to "call" a cline.
Almost all presently existing clines allow this comparison to be fine-grained enough that it trivializes their equational theory. For instance, (cline-default (cline-give-up) (cline-give-up)) and (cline-give-up) can be distinguished this way despite otherwise having equivalent behavior. One exception is that calling cline-flip twice in a row results in a cline that’s ordering-eq to the original.
A call to this dex can be run through pure-run-getfx without problems.
procedure
(cline-by-dex dex) → cline?
dex : dex?
If calls to the given dex can be run through pure-run-getfx without problems, then so can a call to this cline.
When compared by (dex-cline), all cline-by-dex values are ordering-eq if their dexes are.
When the dex obtained from this cline using get-dex-from-cline is compared by (dex-dex), it is ordering-eq to the original dex.
procedure
(cline-give-up) → cline?
A call to this cline can be run through pure-run-getfx without problems.
When the dex obtained from this cline using get-dex-from-cline is compared by (dex-dex), it is ordering-eq to (dex-give-up).
procedure
(cline-default cline-for-trying-first cline-for-trying-second) → cline? cline-for-trying-first : cline? cline-for-trying-second : cline?
For the sake of nontermination, error, and performance concerns, this attempts to compute the result using cline-for-trying-first before it moves on to cline-for-trying-second.
The invocation of cline-for-trying-second is a tail call.
If calls to the given clines can be run through pure-run-getfx without problems, then so can a call to this cline.
When compared by (dex-cline), all cline-default values are ordering-eq if their cline-for-trying-first values are and their cline-for-trying-second values are.
When the dex obtained from this cline using get-dex-from-cline is compared by (dex-dex), it is ordering-eq to the similarly constructed dex-default.
procedure
(cline-opaque name cline) → cline?
name : name? cline : cline?
If calls to the given cline can be run through pure-run-getfx without problems, then so can a call to the resulting cline.
When compared by (dex-cline), all cline-opaque values are ordering-eq if their name values are and their cline values are.
procedure
(cline-by-own-method dexed-getfx-get-method) → cline?
dexed-getfx-get-method : (dexed-first-order/c (-> any/c (getfx/c (maybe/c cline?))))
It invokes the getfx? operation with each value. If any of these invocations results in (nothing), those values are not considered to be in this cline’s domain, and the overall result is (nothing). Otherwise, the computation proceeds:
It checks that the cline methods obtained this way are all ordering-eq. If they’re not, it raises an error. Otherwise, the computation proceeds:
It tail-calls the method.
If the getfx? computations that result from dexed-getfx-get-method and the calls to their resulting clines can be run through pure-run-getfx without problems, then so can a call to this cline.
When compared by (dex-cline), all cline-by-own-method values are ordering-eq if their dexed-getfx-get-method values are.
When the dex obtained from this cline using get-dex-from-cline is compared by (dex-dex), it is ordering-eq to another dex only if that dex was obtained the same way from a cline ordering-eq to this one.
procedure
dexed-getfx-unwrap : (dexed-first-order/c (-> cline? (getfx/c cline?)))
If the getfx? computations that result from dexed-getfx-unwrap and the calls to their resulting clines can be run through pure-run-getfx without problems, then so can a call to this cline.
When compared by (dex-cline), all cline-fix values are ordering-eq if their dexed-getfx-unwrap values are.
When the dex obtained from this cline using get-dex-from-cline is compared by (dex-dex), it is ordering-eq to another dex only if that dex was obtained the same way from a cline ordering-eq to this one.
syntax
(cline-tuple-by-field-position tupler-expr [field-position-nat cline-expr] ...)
tupler-expr : (tupler/c (=/c (length '(cline-expr ...))))
cline-expr : cline?
Each field-position-nat must be a distinct number indicating which field should be checked by the associated cline, and there must be an entry for every field.
For the sake of nontermination, error, and performance concerns, this cline computes by attempting the given clines in the order they appear in this call. If a cline before the last one determines a non-ordering-eq result, the following clines are only checked to be sure their domains contain the respective field values. Otherwise, the last cline, if any, is attempted as a tail call.
If calls to the given clines can be run through pure-run-getfx without problems, then so can a call to this cline.
When compared by (dex-cline), all cline-tuple-by-field-position values are ordering-eq if they’re for the same tupler, if they have field-position-nat values in the same sequence, and if their cline-expr values are ordering-eq.
When the dex obtained from this cline using get-dex-from-cline is compared by (dex-dex), it is ordering-eq to the similarly constructed dex-tuple-by-field-position.
syntax
(cline-tuple tupler-expr cline-expr ...)
tupler-expr : (tupler/c (=/c (length '(cline-expr ...))))
cline-expr : cline?
For the sake of nontermination, error, and performance concerns, this cline computes by attempting the given clines in the order they appear in this call. If a cline before the last one determines a non-ordering-eq result, the following clines are only checked to be sure their domains contain the respective field values. Otherwise, the last cline, if any, is attempted as a tail call.
If calls to the given clines can be run through pure-run-getfx without problems, then so can a call to this cline.
When compared by (dex-cline), each cline-tuple value is ordering-eq to the equivalent cline-tuple-by-field-position value.
When the dex obtained from this cline using get-dex-from-cline is compared by (dex-dex), it is ordering-eq to the similarly constructed dex-tuple.
procedure
(cline-flip cline) → cline?
cline : cline?
If calls to the given cline can be run through pure-run-getfx without problems, then so can a call to the resulting cline.
When compared by (dex-cline), cline-flip values are usually ordering-eq if their given clines are. The one exception is that calling cline-flip twice in a row has no effect; the result of the second call is ordering-eq to the original cline. This behavior is experimental; future revisions to this library may remove this exception or add more exceptions (such as having (cline-flip (cline-default a b)) be ordering-eq to (cline-default (cline-flip a) (cline-flip b))).
When the dex obtained from this cline using get-dex-from-cline is compared by (dex-dex), it is ordering-eq to the dex obtained the same way from the original cline.
2.4 Merges and Fuses
Interconfection offers a non-exhaustive but extensive selection of merges and fuses. These are values which can be compared for equality with like values (using (dex-merge) and (dex-fuse)), and they represent operations of two arguments (invocable using getfx-call-merge and getfx-call-fuse).
Merges represent operations that are commutative, associative, and idempotent, or in other words exactly the kind of operation that can operate on a (nonempty and finite) unordered set of inputs.
Fuses represent operations that are commutative and associative (and not necessarily idempotent). A fuse is ideal for operating on a (nonempty and finite) unordered multiset of inputs.
The idempotence of a merge operation is such that if the two inputs to the merge are ordering-eq by any dex, the result will be ordering-eq to them both by the same dex.
Calling a merge/fuse is a partial operation. A single merge/fuse is associated with certain domains of values it works on, and these domains are disjoint from each other. If it’s given a set/multiset of operands that are all elements of the same domain, it returns a just of another value in that domain. If it’s given a set/multiset of operands that don’t all belong to the same domain, it returns (nothing), even if each operand belongs to some domain it accepts.
procedure
(getfx-call-merge merge a b) → (getfx/c maybe?)
merge : merge? a : any/c b : any/c
procedure
(getfx-call-fuse fuse a b) → (getfx/c maybe?)
fuse : fuse? a : any/c b : any/c
Whether this getfx? computation can be run through pure-run-getfx without problems depends on the given merge/fuse.
For getfx-call-merge, if there is any dex for which the input values are ordering-eq, then the result will be ordering-eq to them both.
A call to this dex can be run through pure-run-getfx without problems.
procedure
(merge-by-dex dex) → merge?
dex : dex?
Note that this tends to be a merge with many domains, one domain for each value accepted by the given dex. In other words, two values that are each accepted by the given dex but which aren’t ordering-eq will not belong to the same domain of this merge, so the result of getfx-call-merge on those values will be (nothing).
If calls to the given dex can be run through pure-run-getfx without problems, then so can a call to this merge.
When compared by (dex-merge), all merge-by-dex values are ordering-eq if their dexes are.
procedure
(merge-by-cline-min cline) → merge?
cline : cline?
If calls to the given cline can be run through pure-run-getfx without problems, then so can a call to this merge.
When compared by (dex-merge), all merge-by-cline-min values are ordering-eq if their clines are. They’re also ordering-eq to (merge-by-cline-max (cline-flip cline)).
procedure
(merge-by-cline-max cline) → merge?
cline : cline?
If calls to the given cline can be run through pure-run-getfx without problems, then so can a call to this merge.
When compared by (dex-merge), all merge-by-cline-max values are ordering-eq if their clines are. They’re also ordering-eq to (merge-by-cline-min (cline-flip cline)).
procedure
(fuse-by-merge merge) → fuse?
merge : merge?
If calls to the given merge can be run through pure-run-getfx without problems, then so can a call to the resulting fuse.
When compared by (dex-fuse), all fuse-by-merge values are ordering-eq if their merges are.
procedure
(merge-opaque name merge) → merge?
name : name? merge : merge?
procedure
(fuse-opaque name fuse) → fuse?
name : name? fuse : fuse?
If calls to the given merge/fuse can be run through pure-run-getfx without problems, then so can a call to the resulting merge/fuse.
When compared by (dex-merge)/(dex-fuse), all merge-opaque/fuse-opaque values are ordering-eq if their name values are and their merge/fuse values are.
procedure
(merge-by-own-method dexed-getfx-get-method) → merge?
dexed-getfx-get-method : (dexed-first-order/c (-> any/c (getfx/c (maybe/c merge?))))
procedure
(fuse-by-own-method dexed-getfx-get-method) → fuse?
dexed-getfx-get-method : (dexed-first-order/c (-> any/c (getfx/c (maybe/c fuse?))))
It invokes the getfx? operation with each value. If any of these invocations results in (nothing), those values are not considered to be in any of this merge’s/fuse’s domains, and the overall result is (nothing). Otherwise, the computation proceeds:
It checks that the merge/fuse methods obtained this way are all ordering-eq. If they’re not, the values are considered to be in multiple disjoint domains, and the overall result is (nothing). Otherwise, the computation proceeds:
It invokes the method. If the result of that invocation is (nothing), the overall result is (nothing). Otherwise, the result of the invocation is of the form (just result), and the computation proceeds:
To ensure that the overall merge/fuse is associative, it invokes the method-getting getfx? operation one more time on result. If it does not obtain a merge/fuse method that’s ordering-eq to the one originally obtained from the inputs, it raises an error. Otherwise, the computation proceeds:
The overall result is (just result).
If the getfx? computations that result from dexed-getfx-get-method and the calls to their resulting merges/fuses can be run through pure-run-getfx without problems, then so can a call to this merge/fuse.
When compared by (dex-merge)/(dex-fuse), all merge-by-own-method/fuse-by-own-method values are ordering-eq if their dexed-get-method values are.
procedure
dexed-getfx-unwrap : (dexed-first-order/c (-> merge? (getfx/c merge?)))
procedure
dexed-getfx-unwrap : (dexed-first-order/c (-> fuse? (getfx/c fuse?)))
If the getfx? computations that result from dexed-getfx-unwrap and the calls to their resulting merges/fuses can be run through pure-run-getfx without problems, then so can a call to this merge/fuse.
When compared by (dex-merge)/(dex-fuse), all merge-fix/fuse-fix values are ordering-eq if their dexed-getfx-unwrap values are.
syntax
(merge-tuple-by-field-position tupler-expr [field-position-nat field-method-expr] ...)
tupler-expr : (tupler/c (=/c (length '(field-method-expr ...))))
field-method-expr : merge?
syntax
(fuse-tuple-by-field-position tupler-expr [field-position-nat field-method-expr] ...)
tupler-expr : (tupler/c (=/c (length '(field-method-expr ...))))
field-method-expr : fuse?
Each field-position-nat must be a distinct number indicating which field should be checked by the associated merge/fuse, and there must be an entry for every field.
If calls to the given merges/fuses can be run through pure-run-getfx without problems, then so can a call to this merge/fuse.
When compared by (dex-merge)/(dex-fuse), all merge-tuple-by-field-position/fuse-tuple-by-field-position values are ordering-eq if they’re for the same tupler, if they have field-position-nat values in the same sequence, and if their field-method-expr values are ordering-eq.
syntax
(merge-tuple tupler-expr field-method-expr ...)
tupler-expr : (tupler/c (=/c (length '(field-method-expr ...))))
field-method-expr : merge?
syntax
(fuse-tuple tupler-expr field-method-expr ...)
tupler-expr : (tupler/c (=/c (length '(field-method-expr ...))))
field-method-expr : fuse?
If calls to the given merges/fuses can be run through pure-run-getfx without problems, then so can a call to this merge/fuse.
When compared by (dex-merge)/(dex-fuse), each merge-tuple/fuse-tuple value is ordering-eq to the equivalent merge-tuple-by-field-position/fuse-tuple-by-field-position value.
2.5 Tables
Interconfection’s tables are similar to Racket hash tables where all the keys are Interconfection dexed values. However, tables are encapsulated in such a way that pure code will always process the table entries in an order-oblivious way. For instance, an Interconfection table cannot be converted to a list in general. This makes tables useful for representing orderless sets that cross API boundaries, where the API client should not be able to depend on accidental details of the set representation.
procedure
(table-empty? x) → boolean?
x : table?
procedure
(table-empty) → table?
procedure
(table-shadow key maybe-val table) → table?
key : dexed? maybe-val : maybe? table : table?
procedure
(getfx-table-map-fuse table fuse key-to-operand) → (getfx/c maybe?) table : table? fuse : fuse? key-to-operand : (-> dexed? getfx?)
If the getfx? computations that result from key-to-operand and calls to the given fuse can be run through pure-run-getfx without problems, then so can the overall computation.
procedure
(getfx-table-sort cline table)
→ (getfx/c (maybe/c (listof table?))) cline : cline? table : table?
What we mean by partitioning is this: Each entry of the original table appears in one and only one table in the list, and the tables have no other entries.
What we mean by ascending order is this: If the given cline computes that one value of the original table is (ordering-lt) to a second value, then the two values are stored in two different tables, and the first value’s table precedes the second value’s table in the list. Likewise (and equivalently), if a value is (ordering-gt) to a second value, the first occurs after the second in the list of tables.
If calls to the given dex can be run through pure-run-getfx without problems, then so can a call to the resulting dex.
When compared by (dex-dex), all dex-table values are ordering-eq if their dex-val values are.
The given keys must be mutually unique.
For the sake of nontermination, error, and performance concerns, this dex computes by attempting the given dexes in the order they appear in the assoc association list. If a dex before the last one determines a non-ordering-eq result, the following dexes are only checked to be sure their domains contain the respective field values. Otherwise, the last dex, if any, is attempted as a tail call.
If calls to the given dexes can be run through pure-run-getfx without problems, then so can a call to this dex.
When compared by (dex-dex), all dex-table-ordered values are ordering-eq if they have the same keys in the same sequence and if the associated dexes are ordering-eq.
The given keys must be mutually unique.
For the sake of nontermination, error, and performance concerns, this cline computes by attempting the given clines in the order they appear in the assoc association list. If a cline before the last one determines a non-ordering-eq result, the following clines are only checked to be sure their domains contain the respective field values. Otherwise, the last cline, if any, is attempted as a tail call.
If calls to the given clines can be run through pure-run-getfx without problems, then so can a call to this cline.
When compared by (dex-cline), all cline-table-ordered values are ordering-eq if they have the same keys in the same sequence and if the associated clines are ordering-eq.
When the dex obtained from this cline using get-dex-from-cline is compared by (dex-dex), it is ordering-eq to the similarly constructed dex-table-ordered.
procedure
(merge-table merge-val) → merge?
merge-val : merge?
procedure
(fuse-table fuse-val) → fuse?
fuse-val : fuse?
If calls to the given merge/fuse can be run through pure-run-getfx without problems, then so can a call to the resulting merge/fuse.
When compared by (dex-merge)/(dex-fuse), all merge-table/fuse-table values are ordering-eq if their merge-val/fuse-val values are.
2.6 Fusable Functions
The dex and cline utilities are good for early detection of equality on inductive information, information that we have access to all at once. For coinductive information – that which we may never see the end of – we cannot detect equality early. However, we may still do things based on an assumption of equality and then enforce this assumption as new information comes to light.
Interconfection uses a dedicated kind of encapsulated data, fusable functions, for this purpose. As the name implies, fusable functions support a fuse operation. This operation returns a new fusable function right away. Subsequent calls to that function work by calling each of the original functions and fusing their results – a computation which can cause errors if the return values turn out not to be as fusable as expected. We can use those errors to enforce our equality assumptions on the fly.
Interconfections’s dexes and clines can’t do this kind of delayed enforcement because they only compute simple values like (ordering-lt).
It’s arguable whether Interconfection’s merges could do this. The property that sets apart a merge from a fuse is that a merge must be idempotent; the result of merging a value with itself must be indistinguishable from the original value. When we fuse a fusable function with itself, we end up with a function that does at least double the amount of computation, so in practice, the original and the fusion will not be indistinguishable. Because of this, Interconfection’s fusable functions only come with a fuse operation, not a merge operation.
An Interconfection fusable-function? is also a procedure? value. It can be invoked just like any other Racket procedure.
There is currently no way to make a fusable function that performs a tail call. This property wouldn’t be preserved by fuse-fusable-function anyway.
procedure
(fusable-function? x) → boolean?
x : any/c
procedure
(make-fusable-function proc) → fusable-function?
proc : (-> any/c getfx?)
If the getfx? computations that result from proc can be run through pure-run-getfx without problems, then so can a call to the resulting fusable function.
procedure
(fuse-fusable-function dexed-getfx-arg-to-method) → fuse?
dexed-getfx-arg-to-method : (dexed-first-order/c (-> any/c (getfx/c fuse?)))
If the getfx? computations that result from dexed-getfx-arg-to-method and the calls to their resulting fuses can be run through pure-run-getfx without problems, then so can a call to the fused fusable function.
A call to this fuse can be run through pure-run-getfx without problems.
When compared by (dex-dex), all fuse-fusable-function values are ordering-eq if their dexed-getfx-arg-to-method values are.
2.7 Contracts for tables
procedure
(table-kv-of unwrapped-k/c v/c) → contract?
unwrapped-k/c : contract? v/c : contract?
Specifically, since the keys of a table are always dexed values, the contract unwrapped-k/c on the keys applies to the unwrapped values of the keys, rather than the keys themselves.
The unwrapped-k/c contract’s projection on each unwrapped key must be ordering-eq to the original unwrapped key. This essentially means the contract must be first-order.
procedure
(table-v-of c) → contract?
c : contract?
2.8 Operations for Other Data Types and Derived Operations
(require interconfection/order) | |
package: interconfection-lib |
The interconfection/order module exports all the definitions of interconfection/order/base plus the definitions below.
procedure
(dex-trivial) → dex?
A call to this dex can be run through pure-run-getfx without problems.
procedure
(dex-boolean) → dex?
A call to this dex can be run through pure-run-getfx without problems.
procedure
A call to this cline can be run through pure-run-getfx without problems.
When the dex obtained from this cline using get-dex-from-cline is compared by (dex-dex), it is ordering-eq to (dex-boolean).
procedure
A call to this cline can be run through pure-run-getfx without problems.
When the dex obtained from this cline using get-dex-from-cline is compared by (dex-dex), it is ordering-eq to (dex-boolean).
procedure
A call to this merge can be run through pure-run-getfx without problems.
procedure
A call to this merge can be run through pure-run-getfx without problems.
procedure
A call to this dex can be run through pure-run-getfx without problems.
procedure
A call to this cline can be run through pure-run-getfx without problems.
When the dex obtained from this cline using get-dex-from-cline is compared by (dex-dex), it is ordering-eq to (dex-immutable-string).
procedure
A call to this dex can be run through pure-run-getfx without problems.
procedure
A call to this cline can be run through pure-run-getfx without problems.
When the dex obtained from this cline using get-dex-from-cline is compared by (dex-dex), it is ordering-eq to (dex-exact-rational).
procedure
A call to this fuse can be run through pure-run-getfx without problems.
procedure
A call to this fuse can be run through pure-run-getfx without problems.
procedure
(assocs->table-if-mutually-unique assocs) → (maybe/c table?)
assocs : (listof (cons/c dexed? any/c))
This is a procedure that is convenient for two purposes: It’s useful for detecting duplicates in a list of dexed values, and it’s useful for constructing tables. These purposes often coincide, since data structures which contain mutually unique values are often good candidates for converting to tables.
procedure
(table-kv-all? table kv-accepted?) → boolean?
table : table? kv-accepted? : (-> dexed? any/c boolean?)
There is no short-circuiting. Every entry is always visited, a policy which ensures that pure code can’t use nontermination or run time errors to make assertions about the iteration order of the table. (Nevertheless, impure code can use Racket side effects to observe the iteration order.)
procedure
(table-kv-any? table kv-accepted?) → boolean?
table : table? kv-accepted? : (-> dexed? any/c boolean?)
There is no short-circuiting. Every entry is always visited, a policy which ensures that pure code can’t use nontermination or run time errors to make assertions about the iteration order of the table. (Nevertheless, impure code can use Racket side effects to observe the iteration order.)
procedure
(table-v-all? table v-accepted?) → boolean?
table : table? v-accepted? : (-> any/c boolean?)
There is no short-circuiting. Every entry is always visited, a policy which ensures that pure code can’t use nontermination or run time errors to make assertions about the iteration order of the table. (Nevertheless, impure code can use Racket side effects to observe the iteration order.)
procedure
(table-v-any? table v-accepted?) → boolean?
table : table? v-accepted? : (-> any/c boolean?)
There is no short-circuiting. Every entry is always visited, a policy which ensures that pure code can’t use nontermination or run time errors to make assertions about the iteration order of the table. (Nevertheless, impure code can use Racket side effects to observe the iteration order.)
3 Extensibility
(require interconfection/extensibility/base) | |
package: interconfection-lib |
This module supplies an effect system designed for deterministic concurrency for the sake of implementing module systems. So that modules don’t have to be able to observe the relative order they’re processed in, this makes use of the orderless table? collections from interconfection/order/base. In turn, some parts of interconfection/order/base are designed to be able to read modular extensions of the currently running program using getfx? effects.
For now, nothing but some trivial getfx? effects are documented. The full system also has "extfx" effects which can read and write to definition spaces.
3.1 Read-only extensibility effects ("getfx")
procedure
(pure-run-getfx effects) → any/c
effects : getfx?
procedure
(getfx-done result) → getfx?
result : any/c
If both the subcomputations performed this way can be run through pure-run-getfx without problems, then so can the overall computation.
procedure
(fuse-getfx dexed-getfx-method) → fuse?
dexed-getfx-method : (dexed-first-order/c (-> (getfx/c fuse?)))
If the getfx? computation that results from dexed-getfx-method and the calls to its resulting fuses can be run through pure-run-getfx without problems, then so can a call to the fused getfx? computation.
A call to this fuse can be run through pure-run-getfx without problems.
When compared by (dex-dex), all fuse-getfx values are ordering-eq if their dexed-getfx-method values are.