Effection
1 Order
1.1 Orderings
ordering-lt
ordering-lt?
ordering-eq
ordering-eq?
ordering-private
ordering-private?
ordering-gt
ordering-gt?
dex-result?
cline-result?
1.2 Names, Dexes, and Dexed Values
name?
dex?
getfx-is-in-dex
getfx-name-of
getfx-dexed-of
getfx-compare-by-dex
dexed?
dexed/  c
dexed-first-order/  c
dexed-get-dex
dexed-get-name
dexed-get-value
dex-name
dex-dex
dex-dexed
dex-give-up
dex-default
dex-opaque
dex-by-own-method
dex-fix
dex-struct-by-field-position
dex-struct
1.3 Clines
cline?
get-dex-from-cline
getfx-is-in-cline
getfx-compare-by-cline
dex-cline
cline-by-dex
cline-give-up
cline-default
cline-opaque
cline-by-own-method
cline-fix
cline-struct-by-field-position
cline-struct
cline-flip
1.4 Merges and Fuses
merge?
fuse?
getfx-call-merge
getfx-call-fuse
dex-merge
dex-fuse
merge-by-dex
merge-by-cline-min
merge-by-cline-max
fuse-by-merge
merge-opaque
fuse-opaque
merge-by-own-method
fuse-by-own-method
merge-fix
fuse-fix
merge-struct-by-field-position
fuse-struct-by-field-position
merge-struct
fuse-struct
1.5 Tables
table?
table-empty?
table-get
table-empty
table-shadow
getfx-table-map-fuse
getfx-table-sort
dex-table
dex-table-ordered
cline-table-ordered
merge-table
fuse-table
1.6 Fusable Functions
fusable-function?
make-fusable-function
fuse-fusable-function
1.7 Contracts for tables
table-v-of
1.8 Operations for Other Data Types and Derived Operations
dex-trivial
dex-boolean
cline-boolean-by-truer
cline-boolean-by-falser
merge-boolean-by-and
merge-boolean-by-or
dex-immutable-string
cline-immutable-string
dex-exact-rational
cline-exact-rational
fuse-exact-rational-by-plus
fuse-exact-rational-by-times
assocs->table-if-mutually-unique
getfx-is-eq-by-dex
table-kv-map
table-kv-all?
table-kv-any?
table-v-map
table-v-all?
table-v-any?
2 Extensibility
2.1 Read-only extensibility effects ("getfx")
getfx?
getfx/  c
pure-run-getfx
getfx-done
getfx-bind
7.4

Effection

Effection is a library for managing side effects in Racket. It supports a certain programming style that’s almost pure, but which also has the ability to introduce handlers for custom side effects.

The notion of purity Effection uses is chosen deliberately, and it’s meant to facilitate commutative extensibility mechanisms by way of quasi-deterministic concurrency.

The notions of side effect in Effection are also chosen according to elaborate reasoning, although at this point they’re very experimental. The most remarkable feature of Effection’s side effects is that the dynamically scoped regions they’re observable in can have dynamically scoped holes inside, which for instance can take undesired side effects out of scope for controlled periods of time.

This is all a work in progress. The only pieces of Effection that are fully implemented at this point are some pure utilities that will come in handy for commutatively merging values.

For a more thorough overview of Effection’s goals, see the readme.

    1 Order

      1.1 Orderings

      1.2 Names, Dexes, and Dexed Values

      1.3 Clines

      1.4 Merges and Fuses

      1.5 Tables

      1.6 Fusable Functions

      1.7 Contracts for tables

      1.8 Operations for Other Data Types and Derived Operations

    2 Extensibility

      2.1 Read-only extensibility effects ("getfx")

1 Order

 (require effection/order/base) package: effection-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 Effection-unsafe 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 Effection-safe code. When Effection-safe code uses getfx-compare-by-cline to compare two values by a cline, it can get several results:

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 effection/order/base are also exported by effection/order.

1.1 Orderings

syntax

ordering-lt

syntax

(ordering-lt)

match expander

(ordering-lt)

procedure

(ordering-lt? v)  boolean?

  v : any/c
Struct-like operations which construct and deconstruct a value that represents the result of a comparison where the first value turned out to be candidly strictly less than the second value.

For the purposes of Effection-unsafe Racket code, every two ordering-lt values are equal?.

syntax

ordering-eq

syntax

(ordering-eq)

match expander

(ordering-eq)

procedure

(ordering-eq? v)  boolean?

  v : any/c
Struct-like operations which construct and deconstruct a value that represents the result of a comparison where the first value turned out to be equal to the second value.

For the purposes of Effection-unsafe Racket code, every two ordering-eq values are equal?.

syntax

ordering-private

syntax

(ordering-private)

match expander

(ordering-private)

procedure

(ordering-private? v)  boolean?

  v : any/c
Struct-like operations which construct and deconstruct a value that represents the result of a comparison where the first value turned out to be secretly strictly less than or secretly strictly greater than the second value.

For the purposes of Effection-unsafe Racket code, every two ordering-private values are equal?.

syntax

ordering-gt

syntax

(ordering-gt)

match expander

(ordering-gt)

procedure

(ordering-gt? v)  boolean?

  v : any/c
Struct-like operations which construct and deconstruct a value that represents the result of a comparison where the first value turned out to be candidly strictly greater than the second value.

For the purposes of Effection-unsafe Racket code, every two ordering-gt values are equal?.

procedure

(dex-result? x)  boolean?

  x : any/c
Returns whether the given value is a possible result for a dex (something that satisfies either ordering-eq? or ordering-private?).

procedure

(cline-result? x)  boolean?

  x : any/c
Returns whether the given value is a possible result for a dex (something that satisfies ordering-lt?, dex-result?, or ordering-gt?).

1.2 Names, Dexes, and Dexed Values

procedure

(name? x)  boolean?

  x : any/c
Returns whether the given value is a name. In Effection, a "name" is something like a partial application of comparison by a dex. Any value can be converted to a name using getfx-name-of if any dex for that value is at hand (and it always converts to the same name regardless of which dex is chosen), and names themselves can be compared using (dex-name).

procedure

(dex? x)  boolean?

  x : any/c
Returns whether the given value is a dex.

procedure

(getfx-is-in-dex dex x)  (getfx/c boolean?)

  dex : dex?
  x : any/c
Given a dex and a value, returns a getfx? computation that computes whether the value belongs to the dex’s domain.

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-name-of dex x)  (getfx/c (maybe/c name?))

  dex : dex?
  x : any/c
Given a dex and a value, returns a getfx? computation. If the value belongs to the dex’s domain, this computation results in a just of a name that the value can be compared by. Otherwise, it results in a nothing.

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-dexed-of dex x)  (getfx/c (maybe/c dexed?))

  dex : dex?
  x : any/c
Given a dex and a value, returns a just of a dexed version of the given value, if the value belongs to the dex’s domain; otherwise returns a nothing. Given a dex and a value, returns a getfx? computation. If the value belongs to the dex’s domain, this computation results in a just of a dexed version of the given value. Otherwise, it results in a nothing.

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
Given a dex and two values, returns a getfx? computation that compares those values according to the dex. The result is (nothing) if either value is outside the dex’s domain.

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? x)  boolean?

  x : any/c
Returns whether the given value is a dexed value.

procedure

(dexed/c c)  contract?

  c : contract?
Returns a contract that recognizes a dexed value and additionally imposes the given contract on its dexed-get-value. That contract’s projection must be ordering-eq to the original value. This essentially means the contract must be first-order.

procedure

(dexed-first-order/c c)  contract?

  c : contract?
Returns a contract that recognizes a dexed value and additionally imposes the first-order behavior of the given contract on its dexed-get-value. It ignores the contract’s higher-order behavior altgoether, so using certain contracts with dexed-first-order/c has little purpose other than documentation value.

This is nearly the same as (dexed/c (contract-first-order c)), but its name is based on c as well.

procedure

(dexed-get-dex d)  dex?

  d : dexed?
Given a dexed value, returns a dex that has a domain consisting of just one value, namely the value of the given dexed value.

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?
Given a dexed value, returns the name of its value.

procedure

(dexed-get-value d)  any/c

  d : dexed?
Given a dexed value, returns its value.

procedure

(dex-name)  dex?

Returns a dex that compares names.

A call to this dex can be run through pure-run-getfx without problems.

procedure

(dex-dex)  dex?

Returns a dex that compares dexes.

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.

procedure

(dex-dexed)  dex?

Returns a dex that compares dexed values.

A call to this dex can be run through pure-run-getfx without problems.

procedure

(dex-give-up)  dex?

Returns a dex over an empty domain.

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?
Given two dexes, returns a dex over the union of their domains.

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?
Given a name and a dex, returns another dex that behaves like the given one but is not equal to it.

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?))))
Given a dexed getfx? operation, returns a dex that works by invoking that operation with each value to get (just dex) or (nothing), verifying that the two dex values are the same, and then proceeding to tail-call that dex value.

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

(dex-fix dexed-getfx-unwrap)  dex?

  dexed-getfx-unwrap : (dexed-first-order/c (-> dex? (getfx/c dex?)))
Given a dexed getfx? operation, returns a dex that works by passing itself to the operation and then tail-calling the resulting 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-struct-by-field-position struct-id
  [field-position-nat dex-expr]
  ...)
 
  dex-expr : dex?
Returns a dex that compares instances of the structure type named by struct-id, and whose field values can be compared by the dexes produced by the dex-expr expressions.

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.

A struct type is only permitted for struct-id if it’s fully immutable and has no super-type.

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-struct-by-field-position values are ordering-eq if they’re for the same structure type descriptor, if they have field-position-nat values in the same sequence, and if their dex-expr values are ordering-eq.

syntax

(dex-struct struct-id dex-expr ...)

 
  dex-expr : dex?
Returns a dex that compares instances of the structure type named by struct-id, and whose field values can be compared by the dexes produced by the dex-expr expressions.

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.

A struct type is only permitted for struct-id if it’s fully immutable and has no super-type.

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-struct value is ordering-eq to the equivalent dex-struct-by-field-position value.

1.3 Clines

procedure

(cline? x)  boolean?

  x : any/c
Returns whether the given value is a cline.

procedure

(get-dex-from-cline cline)  dex?

  cline : cline?
Given a cline, returns a dex over the same domain.

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
Given a cline and a value, returns a getfx? computation that computes whether the value belongs to the cline’s domain.

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
Given a cline and two values, returns a getfx? computation that compares those values according to the cline. The result is (nothing) if either value is outside the cline’s domain.

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

(dex-cline)  dex?

Returns a dex that compares clines.

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?
Returns a cline that compares values by tail-calling the given dex. Since the dex never returns the "candidly precedes" or "candidly follows" results, this cline doesn’t either.

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?

Returns a cline over an empty domain.

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?
Given two clines, returns a cline over the union of their domains. The resulting cline’s ascending order consists of the first cline’s ascending order in its domain, followed by the second cline’s ascending order outside the first cline’s domain.

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?
Given a name and a cline, returns another cline that behaves like the given one but is not equal to it.

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?))))
Given a dexed getfx? operation, returns a cline that works by invoking that operation with each value to get (just cline) or (nothing), verifying that the two cline values are the same, and then proceeding to tail-call that cline value.

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

(cline-fix dexed-getfx-unwrap)  cline?

  dexed-getfx-unwrap : (dexed-first-order/c (-> cline? (getfx/c cline?)))
Given a dexed getfx? operation, returns a cline that works by passing itself to the operation and then tail-calling the resulting 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-struct-by-field-position struct-id
  [field-position-nat cline-expr]
  ...)
 
  cline-expr : cline?
Returns a cline that compares instances of the structure type named by struct-id, and whose field values can be compared by the clines produced by the cline-expr expressions. The comparison is lexicographic, with the most significant comparisons being the cline-expr values that appear earliest in this call.

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.

A struct type is only permitted for struct-id if it’s fully immutable and has no super-type.

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-struct-by-field-position values are ordering-eq if they’re for the same structure type descriptor, 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-struct-by-field-position.

syntax

(cline-struct struct-id cline-expr ...)

 
  cline-expr : cline?
Returns a cline that compares instances of the structure type named by struct-id, and whose field values can be compared by the clines produced by the cline-expr expressions. The comparison is lexicographic, with the most significant comparisons being the cline-expr values that appear earliest in this call.

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.

A struct type is only permitted for struct-id if it’s fully immutable and has no super-type.

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-struct value is ordering-eq to the equivalent cline-struct-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-struct.

procedure

(cline-flip cline)  cline?

  cline : cline?
Returns a cline that compares values by calling the given dex but reverses the "candidly precedes" and "candidly follows" results (ordering-lt and ordering-gt). It dosn’t reverse the "secretly precedes" and "secretly follows" results.

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.

1.4 Merges and Fuses

Effection 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.

procedure

(merge? x)  boolean?

  x : any/c

procedure

(fuse? x)  boolean?

  x : any/c
Returns whether the given value is a merge/fuse.

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
Given a merge/fuse and two values, this getfx? computation combines those values according to the merge/fuse. The result is (nothing) if either value is outside the merge’s/fuse’s domain. Otherwise, the result is (just value) for some value that’s also in the domain.

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.

procedure

(dex-merge)  dex?

procedure

(dex-fuse)  dex?

Returns a dex that compares merges/fuses.

A call to this dex can be run through pure-run-getfx without problems.

procedure

(merge-by-dex dex)  merge?

  dex : dex?
Returns a merge that merges any values that are already ordering-eq according the given dex. The result of the merge is ordering-eq to both of the inputs.

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?
Returns a merge that finds the minimum of any set of values in the given cline’s domain. The result of the merge is ordering-eq to at least one of the inputs, and it’s ordering-lt to the rest.

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?
Returns a merge that finds the maximum of any set of values in the given cline’s domain. The result of the merge is ordering-eq to at least one of the inputs, and it’s ordering-gt to the rest.

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?
Returns a fuse that fuses values by merging them using the given 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?
Given a name and a merge/fuse, returns another merge/fuse that behaves like the given one but is not equal to it.

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?))))
Given a dexed getfx? operation, returns a merge/fuse that works by invoking that operation with each value to get (just method) or (nothing), verifying that the two method values are the same, and invoking that merge/fuse value to get a result of (just result) or (nothing). If the result is (just result), this does a final check before returning it: It invokes the method-getting operation on the result to verify that it obtains the same method value that was obtained from the inputs. This ensures that the operation is associative.

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

(merge-fix dexed-getfx-unwrap)  merge?

  dexed-getfx-unwrap : (dexed-first-order/c (-> merge? (getfx/c merge?)))

procedure

(fuse-fix dexed-getfx-unwrap)  fuse?

  dexed-getfx-unwrap : (dexed-first-order/c (-> fuse? (getfx/c fuse?)))
Given a dexed getfx? operation, returns a merge/fuse that works by passing itself to the operation and then tail-calling the resulting merge/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-struct-by-field-position struct-id
  [field-position-nat field-method-expr]
  ...)
 
  field-method-expr : merge?

syntax

(fuse-struct-by-field-position struct-id
  [field-position-nat fuse-expr]
  ...)
 
  field-method-expr : fuse?
Returns a merge/fuse that combines instances of the structure type named by struct-id, and whose field values can be combined by the merges/fuses produced by the field-method-expr expressions.

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.

A struct type is only permitted for struct-id if it’s fully immutable and has no super-type.

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-struct-by-field-position/fuse-struct-by-field-position values are ordering-eq if they’re for the same structure type descriptor, if they have field-position-nat values in the same sequence, and if their field-method-expr values are ordering-eq.

syntax

(merge-struct struct-id field-method-expr ...)

 
  field-method-expr : merge?

syntax

(fuse-struct struct-id field-method-expr ...)

 
  field-method-expr : fuse?
Returns a merge/fuse that combines instances of the structure type named by struct-id, and whose field values can be combined by the merges/fuses produced by the field-method-expr expressions.

A struct type is only permitted for struct-id if it’s fully immutable and has no super-type.

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-struct/fuse-struct value is ordering-eq to the equivalent merge-struct-by-field-position/fuse-struct-by-field-position value.

1.5 Tables

Effection’s "tables" are similar to Racket hash tables where all the keys are Effection name values. However, tables are encapsulated in such a way that Effection-safe code will always process the table entries in an order-oblivious way. For instance, an Effection 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? x)  boolean?

  x : any/c
Returns whether the given value is an Effection table.

procedure

(table-empty? x)  boolean?

  x : table?
Returns whether the given table is empty.

procedure

(table-get key table)  maybe?

  key : name?
  table : table?
Returns the value associated with the given name in the given table, if any.

procedure

(table-empty)  table?

Returns an empty table.

procedure

(table-shadow key maybe-val table)  table?

  key : name?
  maybe-val : maybe?
  table : table?
Returns another table that’s just like the given one, except that the table-get result for the given name is the given maybe? value. That is, this overwrites or removes the value associated with the given name.

procedure

(getfx-table-map-fuse table    
  fuse    
  key-to-operand)  (getfx/c maybe?)
  table : table?
  fuse : fuse?
  key-to-operand : (-> name? getfx?)
Given a table, a fuse, and a getfx? operation, returns a getfx? computation that calls that operation with each key of the table and results in a just containing the fused value of all the operation’s results. If the table is empty or if any operation result is outside the fuse’s domain, this computation results in (nothing) instead.

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?
Given a cline and a table, returns a getfx? computation that sorts the values of the table by the cline, without determining an order on values that the cline doesn’t determine an order on. This computation results in (nothing) if any of the values are outside the cline’s domain. Otherwise, it results in a just containing a list of nonempty tables, partitioning the original table’s values in ascending order.

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.

procedure

(dex-table dex-val)  dex?

  dex-val : dex?
Returns a dex that compares tables, using the given dex to compare each value.

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.

procedure

(dex-table-ordered assoc)  dex?

  assoc : (listof (list/c name? dex?))
Returns a dex that compares tables that have precisely the given set of names as keys and whose values can be compared by the corresponding dexes.

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 key names in the same sequence and if the associated dexes are ordering-eq.

procedure

(cline-table-ordered assoc)  cline?

  assoc : (listof (list/c name? cline?))
Returns a cline that compares tables that have precisely the given set of names as keys and whose values can be compared by the corresponding clines. The comparison is lexicographic, with the most significant comparisons being the clines that appear earliest in the assoc association list.

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 key names 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?
Returns a merge/fuse that combines tables by collecting all the nonoverlapping entries and combining the overlapping entries using the given merge-val/fuse-val.

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.

1.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.

Effection 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.

Effections’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 Effection’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, Effection’s fusable functions only come with a fuse operation, not a merge operation.

An Effection 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
Returns whether the given value is an Effection fusable function value.

procedure

(make-fusable-function proc)  fusable-function?

  proc : (-> any/c getfx?)
Returns a fusable function that behaves like the given single-input, single-output getfx? operation.

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?)))
Given dexed-getfx-arg-to-method as a dexed function, returns a fuse that combines fusable functions. The combined fusable function works by calling the dexed-getfx-arg-to-method function and running its getfx? result to get a fuse; doing the same with both of the original fusable functions to get each of their results; and fusing the results by that fuse. If the results turn out not to be in the fuse’s domain, this causes an error.

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.

1.7 Contracts for tables

procedure

(table-v-of c)  contract?

  c : contract?
Returns a contract that recognizes a table? where the mapped values obey the given contract.

1.8 Operations for Other Data Types and Derived Operations

 (require effection/order) package: effection-lib

The effection/order module exports all the definitions of effection/order/base plus the definitions below.

procedure

(dex-trivial)  dex?

Returns a dex that compares trivial? values from Lathe Comforts. Every two trivial? values are ordering-eq.

A call to this dex can be run through pure-run-getfx without problems.

procedure

(dex-boolean)  dex?

Returns a dex that compares boolean? values.

A call to this dex can be run through pure-run-getfx without problems.

Returns a cline that compares booleans by an ordering where #f is ordering-lt to #t.

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).

Returns a cline that compares booleans by an ordering where #t is ordering-lt to #f.

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

(merge-boolean-by-and)  merge?

Returns a merge that merges booleans using and.

A call to this merge can be run through pure-run-getfx without problems.

procedure

(merge-boolean-by-or)  merge?

Returns a merge that merges booleans using or.

A call to this merge can be run through pure-run-getfx without problems.

procedure

(dex-immutable-string)  dex?

Returns a dex that compares immutable strings.

A call to this dex can be run through pure-run-getfx without problems.

Returns a cline that compares immutable strings by their string<? ordering.

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

(dex-exact-rational)  dex?

Returns a dex that compares exact rational numbers.

A call to this dex can be run through pure-run-getfx without problems.

procedure

(cline-exact-rational)  cline?

Returns a cline that compares exact rational numbers by their < ordering.

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).

Returns a fuse that fuses exact rational numbers using +.

A call to this fuse can be run through pure-run-getfx without problems.

Returns a fuse that fuses exact rational numbers using *.

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 name? any/c))
Given an association list, returns a just of a table with the same entries if the keys are mutually unique; otherwise returns (nothing).

This is a procedure that is convenient for two purposes: It’s useful for detecting duplicates in a list of names, and it’s useful for constructing tables. These purposes often coincide, since data structures which contain mutually unique names are often good candidates for converting to tables.

procedure

(getfx-is-eq-by-dex dex a b)  (getfx/c boolean?)

  dex : dex?
  a : any/c
  b : any/c
Given a dex and two values, returns a getfx? computation that computes whether those values are ordering-eq according to the dex. The two values must be in the dex’s domain; otherwise, the computation raises an exn:fail:contract? exception.

procedure

(table-kv-map table kv-to-v)  maybe?

  table : table?
  kv-to-v : (-> name? any/c any/c)
Returns a table with the same keys as the given one. The result is constructed by iterating over the given hash table’s entries in an unspecified order and calling the given function with each entry’s key and value to determine the corresponding result entry’s mapped value.

procedure

(table-kv-all? table kv-accepted?)  boolean?

  table : table?
  kv-accepted? : (-> name? any/c boolean?)
Iterates over the given hash table’s entries in an unspecified order and calls the given function on each entry’s key and value. If the function ever returns #f, then the overall result is #f; otherwise, it’s #t.

There is no short-circuiting. Every entry is always visited, a policy which ensures that Effection-safe code can’t use nontermination or run time errors to make assertions about the iteration order of the table. (Nevertheless, Effection-unsafe code can use Racket side effects to observe the iteration order.)

procedure

(table-kv-any? table kv-accepted?)  boolean?

  table : table?
  kv-accepted? : (-> name? any/c boolean?)
Iterates over the given hash table’s entries in an unspecified order and calls the given function on each entry’s key and value. If the function ever returns #t, then the overall result is #t; otherwise, it’s #f.

There is no short-circuiting. Every entry is always visited, a policy which ensures that Effection-safe code can’t use nontermination or run time errors to make assertions about the iteration order of the table. (Nevertheless, Effection-unsafe code can use Racket side effects to observe the iteration order.)

procedure

(table-v-map table v-to-v)  maybe?

  table : table?
  v-to-v : (-> any/c any/c)
Returns a table with the same keys as the given one. The result is constructed by iterating over the given hash table’s entries in an unspecified order and calling the given function with each entry’s mapped value to determine the corresponding result entry’s mapped value.

procedure

(table-v-all? table v-accepted?)  boolean?

  table : table?
  v-accepted? : (-> any/c boolean?)
Iterates over the given hash table’s entries in an unspecified order and calls the given function on each entry’s mapped value. If the function ever returns #f, then the overall result is #f; otherwise, it’s #t.

There is no short-circuiting. Every entry is always visited, a policy which ensures that Effection-safe code can’t use nontermination or run time errors to make assertions about the iteration order of the table. (Nevertheless, Effection-unsafe 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?)
Iterates over the given hash table’s entries in an unspecified order and calls the given function on each entry’s mapped value. If the function ever returns #t, then the overall result is #t; otherwise, it’s #f.

There is no short-circuiting. Every entry is always visited, a policy which ensures that Effection-safe code can’t use nontermination or run time errors to make assertions about the iteration order of the table. (Nevertheless, Effection-unsafe code can use Racket side effects to observe the iteration order.)

2 Extensibility

 (require effection/extensibility/base)
  package: effection-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 effection/order/base. In turn, some parts of effection/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.

2.1 Read-only extensibility effects ("getfx")

procedure

(getfx? v)  boolean?

  v : any/c
Returns whether the given value is a representation of an effectful computation that performs read-only extensibility side effects as it computes a result.

procedure

(getfx/c result/c)  contract?

  result/c : contract?
Returns a contract that recognizes a representation of an effectful computation taht performs read-only extensibility side effects and returns a value that abides by the given contract.

procedure

(pure-run-getfx effects)  any/c

  effects : getfx?
Attempts to run the given getfx? computation, raising an error if it attempts to read just about anything.

procedure

(getfx-done result)  getfx?

  result : any/c
Returns a getfx? computation that performs no side effects and has the given result.

procedure

(getfx-bind effects then)  getfx?

  effects : getfx?
  then : (-> any/c getfx?)
Returns a getfx? computation that proceeds by running the given effects getfx? computation, passing its result to then, and finally running the getfx? computatin that results from that.

If both the subcomputations performed this way can be run through pure-run-getfx without problems, then so can the overall computation.