pub enum Op {
Show 20 variants
State,
Session,
Presence,
Awareness,
Const,
Pointwise {
code: Core,
},
MapValues,
MapList {
f: Fun,
},
FilterList {
f: Fun,
},
SortBy {
f: Fun,
},
Concat,
FlatMap {
f: Fun,
},
Flatten,
Count,
IsEmpty,
Join {
key: Fun,
matched: Matching,
},
ArrangeBy {
key: Fun,
},
GroupBy {
key: Fun,
of: Fun,
agg: Agg,
},
Restrict {
key: Fun,
keep: Presence,
},
Distinct,
}Expand description
What one operator does.
Variants§
State
The durable accumulator, supplied by the caller. The plan’s one source.
Session
The subscriber’s Session. Everything not downstream of it is shareable between
subscribers (§5.3), and everything downstream of it is that subscriber’s — including when
what moved is the route rather than the actor, which is the one field of a session that
changes while a subscription is open (crate::render::SessionUse).
Presence
Who is connected — presence(), supplied by the caller like the other two sources.
Everything downstream of it is per subscriber even though the value is the same for
everybody, and the reason is a clock rather than a privacy rule: the shared dataflow is
versioned by the log’s seq (crate::engine::SharedDataflow), and presence moves when
the log does not. Sharing it would need a second version, which is
docs/48 §48.13’s first unbuilt item.
Awareness
What everybody is doing — awareness(f), supplied by the caller like the other sources.
Op::Presence’s rules, for Op::Presence’s reason: a roster with a payload is not a
function of the accumulator either, and the shared dataflow is versioned by the log’s
seq. A separate source rather than a field of the roster because the two move
independently — a client that moves its cursor changes this and not presence.
Const
A closed expression, evaluated once when the plan is prepared.
Pointwise
Recomputed when an input changed. Carries a Lam over its inputs.
MapValues
map_values(m) — where every delta in a Beck program is born, because the accumulator is a
value and a plan consumes changes. crate::pmap::PMap::diff is the conversion.
MapList
FilterList
SortBy
Concat
concat_lists([a, b, …]) — a union of delta streams, one per named part.
FlatMap
concat_lists(map_list(xs, f)) as one operator — what crate::fuse makes of the pair,
and the shape every for loop in a ui: block has. Applies f and takes the resulting
list apart in one step, so the list of lists in between is never arranged.
Flatten
concat_lists(xs) where xs is itself a collection of lists: a flatten.
A for loop in a ui: block lowers to concat_lists(map_list(todos, …)) and
crate::fuse turns that pair into Op::FlatMap, so this is what remains when the
collection of lists came from somewhere else — a map_values whose values are lists, a
sort_by, or a map_list the fusion refused.
Count
list_len — §3.8’s remaining. The arrangement’s size, so ±1 per delta and never a
recount; and it does not force its input to be materialised.
IsEmpty
Join
The join a loop already contained: for x in xs: whose body asks map_get(m, k(x)).
docs/99 §99.6 — the
algebra’s first binary operator, and the reason it is not a syntax: the two programs in
the tree that relate two collections already say what they mean, and what they were missing
was an operator to say it to. crate::relate is the recognition.
Two inputs, and they are not symmetric. The left is the collection being looped over.
The right is an index: an arrangement whose key’s first component is the join key,
which Op::MapValues over a Map already is. One left row matches at most one right row,
because an arrangement’s keys are unique by construction (§99.5 decision 2), so this is an
outer equi-join on a unique key and every left row appears exactly once in the output —
with the match, or without one, which is what map_get’s Option means.
Maintained from both sides (§99.5’s bilinear rule): a left row that moved is re-looked up, and a right row that moved reaches exactly the left rows whose key it answers, through a reverse index this operator keeps. Neither costs the collection.
Fields
key: FunThe join key, as a function of the left element alone. It captures nothing —
crate::relate refuses the shape otherwise — which is what makes the operator’s own
work O(δ) rather than O(n).
ArrangeBy
A second index over a collection, keyed by something other than what orders it — §99.5
decision 4’s arrange_by, and docs/99
§99.9 item 3.
It is the right side of a Op::Join whose left side asked for a group: the collection
the program wrote filter_list(xs, lambda y: by(y) == …) over, arranged so that the
equality is a range rather than a scan.
Its arrangement is Op::SortBy’s, and that is worth saying rather than hiding. Both
key an element by f(x) followed by the input’s key, so both are one BTreeMap in which
equal keys keep the order they arrived in. A sort is that arrangement iterated; an index
is that arrangement probed. The engine runs one function for the two, and what differs is
the consumer — which is why they are two operators rather than one with a flag: nothing may
fuse a probe the way it fuses a sort, and beck explain query should not tell a reader
their program sorts when it does not.
Fields
GroupBy
One value per group, maintained — docs/99
§99.9 item 6’s group by, and the operator that answers a question about a group
without the group existing.
Its output is an arrangement keyed by the group’s key alone, holding the aggregate, so a
Op::Join probes it with Matching::Unique exactly as it probes a map_values — the
answer is Some(x) for a group with rows and None for one without, which is what
list_min of a list and of an empty list already return.
It is not an index and it does not arrange the collection. Op::ArrangeBy keys every
row so that a range answers with the group; this keeps, per group, only as much as its
aggregate needs — a multiset of what the rows projected to for an extreme, a running total
for a sum. A row that arrives moves that and nothing else, and the aggregate moves or it
does not: an event that does not change the answer emits no change and nothing downstream
runs. A sum is the aggregate that takes no discount there, because every row that joins
its group changes it.
Both ends of that multiset are reachable, and that is the finding. §99.9 item 6 expected
min and max to be asymmetric, because a prefix range of somebody else’s arrangement
can be entered from its start and not from its end: bounding (g, y) above needs a
successor of an arbitrary crate::Value and there is none. A tree this operator builds
itself is keyed by the projection alone and is bounded at both ends by construction, so
max costs what min costs. The asymmetry belonged to the design rather than to the
problem.
Fields
Restrict
The left rows an index answers, or the ones it does not — the algebra’s difference, and
the intersection that is its complement
(docs/99 §99.9 item 7).
The program wrote filter_list(xs, lambda x: map_contains(m, k(x))), or its negation, and
what that costs today is Op::FilterList’s rebuild rule: a predicate that reads m is a
different predicate whenever m moves, so a payment arriving reconsiders every invoice.
crate::relate::restriction is the recognition, and there is no syntax for the operator
for Op::Join’s reason.
It is the one binary operator whose output is one of its inputs, and that is the whole
of §99.5 decision 2’s “no representational change at all”. A Op::Join emits a row —
the left value and what it matched — so the collection below it holds something the program
did not write, which is why a filter_list cannot become one: its consumers read the
element. This emits the left element under the left key, so what a consumer reads is what
the filter_list gave it, entry for entry.
Maintained from both sides, as §99.5’s bilinear rule requires, and the right half is
the one no single-collection test can see: an entry arriving in the index takes rows out
of a difference and puts them into an intersection, through the same reverse index
Op::Join keeps. A left row that moved is one probe.
It holds no copy of its left input, which is what lets a row this operator dropped come back when the index entry that dropped it leaves. The value is read from the left input itself — its arrangement, or the shadow this operator already keeps of a plain list — so the state here is a join key per left row and the reverse index, and never a row.
Fields
Distinct
The values in a collection, each once — the algebra’s δ, and the last row of §99.4
(docs/99 §99.9 item 7).
list_unique(xs), which is lib/collections.beck’s unique: the first occurrence of each
value, in the order the input held them. The order is the decision and not a detail.
The library has a second duplicate-free list — elements(set_of(xs)), which is sorted — and
both are maintainable; taking the answer a program already had rather than inventing a third
is the test a second spelling of an old operation has to pass, which is list_sum’s rule
applied to an order instead of to a total.
So the output’s key is an input key: the smallest one holding each value. That makes the
output a sub-order of the input’s, exactly as Op::FilterList’s is, and it is why nothing
downstream had to learn anything — a consumer reads the values in first-occurrence order
because that is what iterating the arrangement gives.
What moves is the interesting half. A value arriving before its own standing first
occurrence moves the published entry — the only operator here whose output entry can change
key without the value changing — and one leaving promotes the next occurrence rather than
dropping the value. Both are O(log n), because the operator keeps the input keys holding
each value in an ordered set and reads one end of it.
It carries no per-element function: a projection is a Op::MapList above it, which is how
the program wrote it.
Implementations§
Source§impl Op
impl Op
pub fn name(&self) -> &'static str
Sourcepub fn maintained(&self) -> bool
pub fn maintained(&self) -> bool
Whether this operator is maintained by delta rather than recomputed.
Sourcepub fn is_source(&self) -> bool
pub fn is_source(&self) -> bool
Whether this is an input to the dataflow rather than a step in it.
Sourcepub fn key(&self) -> &'static str
pub fn key(&self) -> &'static str
What orders this operator’s arrangement — the table in this module’s own documentation, as
a sentence, so beck explain query states the thing that makes the output order a
consequence of the plan rather than of a sort at the end.
Sourcepub fn is_arrangement(&self) -> bool
pub fn is_arrangement(&self) -> bool
Whether this operator’s output is an arrangement rather than a value.
Sourcepub fn funs_mut(&mut self) -> Vec<&mut Fun>
pub fn funs_mut(&mut self) -> Vec<&mut Fun>
Every per-element function this operator carries, whatever each is applied to.
One accessor rather than the five-way if let that was written out at each of the four
places that remap captures: a new operator with a Fun missed at one of them would be a
capture the plan never renumbered, which is a wrong OpId rather than a compile error.
It returns a list because Op::GroupBy carries two, and an accessor that returned the
first would reintroduce exactly the defect the paragraph above describes — silently, for
the second one only.