Op

Enum Op 

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

Fields

§code: Core
§

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

Fields

§

FilterList

Fields

§

SortBy

Fields

§

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.

Fields

§

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: Fun

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

§matched: Matching

What one probe of the right side returns, which is decided by which index is on it.

§

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

§key: Fun

What to key by, as a function of one element. It captures nothing, for Op::Join’s reason.

§

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

§key: Fun

The group’s key, as a function of one row. It captures nothing, for Op::Join’s reason.

§of: Fun

What each row contributes to its group — the projection under the aggregate, and the identity when the program asked about the rows themselves.

§agg: Agg

Which end of the group is wanted.

§

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

§key: Fun

The key to probe the index by, as a function of the left element alone. It captures nothing, for Op::Join’s reason.

§keep: Presence

Which answer keeps the row.

§

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

Source

pub fn name(&self) -> &'static str

Source

pub fn maintained(&self) -> bool

Whether this operator is maintained by delta rather than recomputed.

Source

pub fn is_source(&self) -> bool

Whether this is an input to the dataflow rather than a step in it.

Source

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.

Source

pub fn is_arrangement(&self) -> bool

Whether this operator’s output is an arrangement rather than a value.

Source

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.

Source

pub fn funs(&self) -> Vec<&Fun>

The same functions, borrowed. Order is the order the engine prepares them in.

Trait Implementations§

Source§

impl Clone for Op

Source§

fn clone(&self) -> Op

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Op

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Op

§

impl RefUnwindSafe for Op

§

impl Send for Op

§

impl Sync for Op

§

impl Unpin for Op

§

impl UnwindSafe for Op

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.