Effects¶
Typed effect interfaces, lexical rows, and handler signatures.
effects
¶
Typed effect interfaces, lexical rows, and handler signatures for QIEC.
EMPTY_ROW
module-attribute
¶
EMPTY_ROW = EffectRow()
The row of a computation that performs no effects.
RowVariable
dataclass
¶
RowVariable(name: str, identity: RowVariableId, lacks: tuple[EffectInstanceId, ...] = ())
The open tail of an effect row, with the instances it excludes.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Display name, used in diagnostics only. Two variables with the same identity and different names are the same variable.
TYPE:
|
identity
|
Stable identity. Equality and hashing are taken from this rather
than from
TYPE:
|
lacks
|
Instances the tail is proven not to contain. Sorted at construction, so equality is insensitive to the order given.
TYPE:
|
__post_init__
¶
Sort the lacks constraints and reject a malformed variable.
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
Source code in src/quivers/qiec/effects.py
proves_lacks
¶
proves_lacks(instance: EffectInstanceId) -> bool
Whether this tail is known not to contain instance.
| PARAMETER | DESCRIPTION |
|---|---|
instance
|
The lexical instance to test.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True when the constraint is carried, which is what permits an
explicit entry for |
Source code in src/quivers/qiec/effects.py
__eq__
¶
Compare by identity and constraints, ignoring the display name.
| PARAMETER | DESCRIPTION |
|---|---|
other
|
The value to compare against.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True when |
Source code in src/quivers/qiec/effects.py
__hash__
¶
Hash the fields that __eq__ compares.
| RETURNS | DESCRIPTION |
|---|---|
int
|
A hash of the identity and the lacks constraints. |
merge
¶
merge(other: RowVariable) -> RowVariable
Unify two occurrences, accumulating their lacks constraints.
| PARAMETER | DESCRIPTION |
|---|---|
other
|
Another occurrence of the same variable.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
RowVariable
|
One variable carrying the union of both constraint sets, which is sound because each occurrence's constraints hold of the single variable they both name. |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If the two variables have different identities, and so are not occurrences of one variable at all. |
Source code in src/quivers/qiec/effects.py
with_lacks
¶
with_lacks(instances: tuple[EffectInstanceId, ...]) -> RowVariable
Return this variable with further instances excluded.
| PARAMETER | DESCRIPTION |
|---|---|
instances
|
Instances to add to the constraint set. Instances already excluded are absorbed rather than repeated.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
RowVariable
|
A variable of the same identity carrying the wider constraint. |
Source code in src/quivers/qiec/effects.py
RowEntry
dataclass
¶
RowEntry(instance: EffectInstanceId, effect: EffectRef)
One lexical effect instance and the interface it implements.
| PARAMETER | DESCRIPTION |
|---|---|
instance
|
The lexical instance. Two instances of one interface stay
distinct, so this rather than
TYPE:
|
effect
|
The saturated interface application the instance implements.
TYPE:
|
EffectRow
dataclass
¶
EffectRow(entries: tuple[RowEntry, ...] = (), tail: RowVariable | None = None)
A finite map from lexical instance IDs to interfaces plus a tail.
Entries are sorted by stable identifier at construction time, so row equality is insensitive to source order. Two instances of the same interface remain distinct entries.
| PARAMETER | DESCRIPTION |
|---|---|
entries
|
The instances the row names, each with its interface; any order is accepted and normalized.
TYPE:
|
tail
|
A row variable standing for further entries, or
TYPE:
|
__post_init__
¶
Sort the entries and reject a row that cannot be well formed.
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If one lexical instance appears twice, or if the row is open and its tail does not prove it lacks every explicit entry. Without that proof the tail could later be instantiated to a row repeating an instance already named here. |
Source code in src/quivers/qiec/effects.py
lookup
¶
lookup(instance: EffectInstanceId) -> EffectRef | None
The interface an instance implements in this row.
| PARAMETER | DESCRIPTION |
|---|---|
instance
|
The lexical instance to resolve.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
EffectRef or None
|
The interface when the row carries the instance explicitly, and None otherwise. An open tail is not consulted, so None means absent from the explicit entries rather than absent from every instantiation of the row. |
Source code in src/quivers/qiec/effects.py
contains
¶
contains(instance: EffectInstanceId) -> bool
Whether the row carries an explicit entry for an instance.
| PARAMETER | DESCRIPTION |
|---|---|
instance
|
The lexical instance to test.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True when an explicit entry names the instance. |
Source code in src/quivers/qiec/effects.py
add
¶
Return this row extended with one entry.
| PARAMETER | DESCRIPTION |
|---|---|
entry
|
The instance and interface to add. Re-adding an entry the row already carries returns the row unchanged, so the operation is idempotent.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
EffectRow
|
The extended row. When the row is open, its tail gains a lacks
constraint for the new instance, which keeps the invariant
|
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If the row already binds this instance to a different interface, since one lexical instance implements one interface. |
Source code in src/quivers/qiec/effects.py
without
¶
without(instance: EffectInstanceId) -> EffectRow
Return this row with one instance discharged.
| PARAMETER | DESCRIPTION |
|---|---|
instance
|
The lexical instance to remove, as a handler does when it handles every operation of that instance.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
EffectRow
|
The row without that entry, keeping the tail as it was. |
| RAISES | DESCRIPTION |
|---|---|
KeyError
|
If the row carries no explicit entry for the instance. This is an error rather than a no-op because discharging an effect the row never had indicates the caller has the wrong row. |
Source code in src/quivers/qiec/effects.py
union
¶
Union two rows, preserving a compatible open tail.
| PARAMETER | DESCRIPTION |
|---|---|
other
|
The row to merge in.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
EffectRow
|
A row carrying every entry of both. When both are open their tails are merged, and the surviving tail is constrained to lack every explicit instance on either side. |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If the two rows bind one instance to different interfaces, or if their tails are distinct variables and so cannot merge. |
Source code in src/quivers/qiec/effects.py
RowSubstitution
dataclass
¶
RowSubstitution(bindings: tuple[tuple[RowVariableId, EffectRow], ...] = ())
A finite, occurs-checked substitution for stable row variables.
| PARAMETER | DESCRIPTION |
|---|---|
bindings
|
What each row variable stands for. At most one binding per variable, and no binding whose row is tailed by the variable it replaces.
TYPE:
|
__post_init__
¶
Reject a substitution that is ambiguous or immediately cyclic.
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If one variable is bound twice, or if a variable is bound to a row whose own tail is that same variable. The second is the occurs check at depth one; deeper cycles are caught while applying the substitution. |
Source code in src/quivers/qiec/effects.py
lookup
¶
lookup(variable: RowVariable) -> EffectRow | None
The row a variable stands for under this substitution.
| PARAMETER | DESCRIPTION |
|---|---|
variable
|
The variable to resolve, matched on its identity rather than its display name.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
EffectRow or None
|
The bound row, or None when the variable is unbound and so remains an open tail. |
Source code in src/quivers/qiec/effects.py
apply
¶
Replace this row's tail, and any tail it resolves to.
| PARAMETER | DESCRIPTION |
|---|---|
row
|
The row to substitute into. A closed row is returned as is.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
EffectRow
|
The row with its tail resolved as far as the bindings reach. |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If the bindings are cyclic, or if resolving a tail would introduce an instance that tail was proven to lack. |
Source code in src/quivers/qiec/effects.py
RowUnification
dataclass
¶
RowUnification(row: EffectRow, substitution: RowSubstitution = RowSubstitution())
The common row two rows unify to, and how their tails were bound.
| PARAMETER | DESCRIPTION |
|---|---|
row
|
The unified row.
TYPE:
|
substitution
|
The bindings that carry each input row to
TYPE:
|
ComputationType
dataclass
¶
ArgumentDef
dataclass
¶
ArgumentDef(name: str, type: TypeExpr)
One named, typed value argument of an operation.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
The argument's display name.
TYPE:
|
type
|
The argument's type, which may mention the operation's telescope.
TYPE:
|
OperationDef
dataclass
¶
OperationDef(id: OperationId, name: str, telescope: Telescope, arguments: tuple[ArgumentDef, ...], result_type: TypeExpr)
One result-indexed request constructor in an effect interface.
| PARAMETER | DESCRIPTION |
|---|---|
id
|
Stable identity, derived from the owning effect and this operation's name.
TYPE:
|
name
|
Source name, as written in the effect declaration.
TYPE:
|
telescope
|
Static binders this operation introduces on top of the interface's own. They may not shadow the interface binders.
TYPE:
|
arguments
|
Value arguments the request carries, named uniquely.
TYPE:
|
result_type
|
What resuming the request supplies, possibly mentioning the binders above.
TYPE:
|
__post_init__
¶
Validate the telescope and the argument names.
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If the telescope is malformed, or two arguments share a name. |
Source code in src/quivers/qiec/effects.py
EffectDef
dataclass
¶
EffectDef(ref: EffectRef, telescope: Telescope, operations: tuple[OperationDef, ...])
An operation interface.
Identity is nominal: the declaration is fixed by its module and its
own name, carried in ref. Static arguments distinguish applications
of the interface without changing which declaration they apply.
| PARAMETER | DESCRIPTION |
|---|---|
ref
|
The declaration's own reference, which must be unsaturated: a declaration names the interface, and applying it is what supplies arguments.
TYPE:
|
telescope
|
Static binders the interface takes.
TYPE:
|
operations
|
The declared operations, unique by both identity and name.
TYPE:
|
__post_init__
¶
Validate the declaration against its operations.
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If the telescope is malformed, if |
Source code in src/quivers/qiec/effects.py
apply
¶
apply(arguments: tuple[StaticArgument, ...]) -> EffectRef
Instantiate the interface telescope as a concrete effect.
| PARAMETER | DESCRIPTION |
|---|---|
arguments
|
Static arguments to saturate the telescope with.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
EffectRef
|
The saturated application, carrying this declaration's identity together with the arguments given. |
| RAISES | DESCRIPTION |
|---|---|
TypeError
|
If an argument is of the wrong static class for its binder. |
ValueError
|
If the argument count does not match the telescope, or an argument is ill-kinded. |
Source code in src/quivers/qiec/effects.py
matches
¶
matches(effect: EffectRef) -> bool
Whether a concrete effect is a well-kinded application here.
| PARAMETER | DESCRIPTION |
|---|---|
effect
|
The saturated application to test.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True when |
Source code in src/quivers/qiec/effects.py
operation
¶
operation(operation: OperationId) -> OperationDef
The declared operation with a given identity.
| PARAMETER | DESCRIPTION |
|---|---|
operation
|
The identity to resolve.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
OperationDef
|
The matching declaration. |
| RAISES | DESCRIPTION |
|---|---|
KeyError
|
If this interface declares no such operation, which is how a request naming an operation another effect owns is caught. |
Source code in src/quivers/qiec/effects.py
ResumptionGrade
¶
Bases: str, Enum
Quantitative use allowed for a handler-clause resumption.
HandlerClauseDef
dataclass
¶
HandlerClauseDef(operation: OperationId, grade: ResumptionGrade, parameters: tuple[Local, ...] = (), body: Computation | None = None)
One operation a handler covers, and how it may resume.
| PARAMETER | DESCRIPTION |
|---|---|
operation
|
The operation this clause handles.
TYPE:
|
grade
|
How often the clause may invoke its continuation. The grade is part of the clause contract and is checked against the body.
TYPE:
|
parameters
|
Binders for the operation's value arguments, in declaration order. The body refers to the request's arguments through these.
TYPE:
|
body
|
What the clause does.
TYPE:
|
HandlerReturnClauseDef
dataclass
¶
HandlerReturnClauseDef(binder: Local, body: Computation)
What a handler does with a value its computation returns.
Separate from an operation clause because it answers a different thing: an operation clause responds to a request and may resume, while this responds to the computation finishing and has no continuation to invoke.
| PARAMETER | DESCRIPTION |
|---|---|
binder
|
Names the returned value inside the body.
TYPE:
|
body
|
What the handler produces from it. There is no signature-only form: a handler that answers returns at all has to say how.
TYPE:
|
HandlerDef
dataclass
¶
HandlerDef(id: HandlerId, name: str, effect: EffectRef, clauses: tuple[HandlerClauseDef, ...], input_type: TypeExpr, output_type: TypeExpr, introduced: EffectRow = EMPTY_ROW, total: bool = True, forwards_unknown: bool = False, telescope: Telescope = (), return_clause: HandlerReturnClauseDef | None = None, implementation: Literal['authored', 'foreign'] = 'foreign')
Serializable type-and-coverage signature for a handler.
An authored handler carries its clause bodies in the stable core; a
foreign handler's bodies are runtime attachments keyed by id.
| PARAMETER | DESCRIPTION |
|---|---|
id
|
The handler's stable identity.
TYPE:
|
name
|
The handler's display name.
TYPE:
|
effect
|
The interface application the handler handles.
TYPE:
|
clauses
|
One clause per operation the handler covers.
TYPE:
|
input_type
|
The result type of the computation the handler accepts.
TYPE:
|
output_type
|
The type of the handler's answer.
TYPE:
|
introduced
|
The effects the handler's own clauses perform, which the residual row of a handled computation gains. |
total
|
Whether the clauses cover every operation of
TYPE:
|
forwards_unknown
|
Whether an operation no clause covers is forwarded outward rather
than rejected; incompatible with
TYPE:
|
telescope
|
The static parameters the handler abstracts over, in order.
TYPE:
|
return_clause
|
How the handler answers the computation's return, or
TYPE:
|
implementation
|
TYPE:
|
__post_init__
¶
Validate the clause set against the coverage claim.
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If the telescope is malformed, if two clauses cover the same operation, if the handler claims to be both total and forwarding, or if its bodies disagree with its declared implementation. A total handler covering every operation has nothing left to forward. An authored handler missing a body has nowhere for that operation's behavior to live, and a body on a foreign handler would be ignored in favour of its runtime provider; both are worse than being rejected. |
Source code in src/quivers/qiec/effects.py
clause
¶
clause(operation: OperationId) -> HandlerClauseDef | None
The clause covering an operation, if this handler has one.
| PARAMETER | DESCRIPTION |
|---|---|
operation
|
The operation to look for.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
HandlerClauseDef or None
|
The clause, or None when the operation is uncovered. For a forwarding handler None means the request passes through rather than that it is an error. |
Source code in src/quivers/qiec/effects.py
EffectRequest
dataclass
¶
EffectRequest(instance: EffectInstanceId, effect: EffectRef, operation: OperationId, static_arguments: tuple[StaticArgument, ...], arguments: tuple[Value, ...], result_type: TypeExpr, origin: SiteProvenance, tag: Literal['effect_request'] = 'effect_request')
A typed request allocated before filtering or handler dispatch.
| PARAMETER | DESCRIPTION |
|---|---|
instance
|
The effect instance the request is addressed to.
TYPE:
|
effect
|
The instance's interface application.
TYPE:
|
operation
|
The operation requested.
TYPE:
|
static_arguments
|
The operation's telescope instantiation, in binder order.
TYPE:
|
arguments
|
The value arguments, one per operation argument.
TYPE:
|
result_type
|
The type of the value the operation returns under the instantiation.
TYPE:
|
origin
|
Where the request was written and how it was reached.
TYPE:
|
tag
|
The serialization discriminator; always
TYPE:
|
unify_effect_rows
¶
unify_effect_rows(left: EffectRow, right: EffectRow) -> RowUnification
Unify two rows, returning their common row and tail substitution.
Unification is symmetric and driven by the tails. Two closed rows unify only when already equal. One open row unifies with a closed one by binding its tail to the entries it lacks. Two open rows with distinct tails unify by binding both to a fresh tail carrying every instance either side names.
| PARAMETER | DESCRIPTION |
|---|---|
left
|
One row to unify.
TYPE:
|
right
|
The other row. The result does not depend on the order of the two arguments.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
RowUnification
|
The common row and the substitution taking both inputs to it. |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If the rows bind one instance to different interfaces, if two closed rows differ, if an open row carries an entry the closed row lacks, if one side needs an instance the other's tail is proven to lack, or if the computed substitution fails to carry both inputs to the unified row. |
Source code in src/quivers/qiec/effects.py
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 | |
instantiate_effect
¶
instantiate_effect(effect: EffectRef, *, module: str, lexical_path: tuple[str | int, ...]) -> RowEntry
Allocate a deterministic lexical instance of an effect interface.
The instance identity is derived rather than generated, so the same interface allocated at the same place in the same module is the same instance on every run. That is what lets a serialized row refer to an instance across processes.
| PARAMETER | DESCRIPTION |
|---|---|
effect
|
The saturated interface application to allocate.
TYPE:
|
module
|
Module the allocation occurs in. Part of the identity, so two modules allocating at the same lexical path stay distinct.
TYPE:
|
lexical_path
|
Position of the allocation within the module. Two allocations of one interface at different paths are different instances, which is what makes two instances of the same effect distinguishable.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
RowEntry
|
The fresh instance paired with the interface it implements, ready to add to a row. |
Source code in src/quivers/qiec/effects.py
render_instance
¶
render_instance(identity: EffectInstanceId, names: Mapping[EffectInstanceId, str] | None = None) -> str
Render one effect instance in the surface spelling.
| PARAMETER | DESCRIPTION |
|---|---|
identity
|
The instance.
TYPE:
|
names
|
The source name of each lexical instance.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
Its source name, or |
Source code in src/quivers/qiec/effects.py
render_row
¶
render_row(row: EffectRow, names: Mapping[EffectInstanceId, str] | None = None) -> str
Render an effect row in the surface spelling.
| PARAMETER | DESCRIPTION |
|---|---|
row
|
The row.
TYPE:
|
names
|
The source name of each lexical instance; an instance with no name renders as the first eight characters of its digest.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
|