|
Dedekind 0.0.0
A structuralist C++23 Module Library
|
Monad comprehensions over a finite chain: the queries of SQL and LINQ, where, select, order_by and take, and the folds that end them.
More...
#include <algorithm>#include <array>#include <concepts>#include <cstddef>#include <cstdint>#include <cstdlib>#include <functional>#include <limits>#include <optional>#include <ranges>#include <span>#include <type_traits>#include <utility>#include <vector>#include <version>import dedekind.category:periodic;
Classes | |
| struct | dedekind::category::detail_comprehension::StepFrom< Op, R > |
| A fold step with its accumulator bound, \(a \mapsto
\mathit{op}(\mathit{acc}, a)\): the present case of the eliminator. More... | |
| struct | dedekind::category::detail_comprehension::Tally< N > |
| One more element into a count. More... | |
| struct | dedekind::category::detail_comprehension::Extended< F > |
A Kleisli arrow into Maybe extended to the elements of a query, \(\kappa(-, f) : \mathit{Maybe}\,A \to \mathit{Maybe}\,B\). More... | |
| struct | dedekind::category::Lifted< Op > |
A fold step lifted through Maybe: Maybe's eliminator with the cases \([\mathit{acc}, \mathit{op}(\mathit{acc}, \cdot)]\), a hole leaving the accumulator as it is. Written once, so the folds that end a query take their plain steps and none cases on a hole. More... | |
| struct | dedekind::category::Where< P > |
where(p), SQL's WHERE: the Kleisli arrow into Maybe that keeps an element where p holds, a hole where it does not. The walk keeps its length, so a filtered chain is a chain in the same order. More... | |
| struct | dedekind::category::detail_comprehension::SubrangeAt< R > |
| struct | dedekind::category::Windows |
windows(n), the stage that reads each position's window of n elements, a position with its context, as a comonad's \(\delta\) would on an infinite stream; on a finite range the last n - 1 positions have no full window, so it is not a comonad here. It is std::views::slide(n), spelled as a stage. More... | |
| struct | dedekind::category::detail_comprehension::KernelAt< T, K > |
| The kernel at one more prefix. More... | |
| struct | dedekind::category::LastOf< T > |
| The history comonad's counit: a prefix's last letter. More... | |
| struct | dedekind::category::detail_comprehension::LengthOf |
| A kernel for the laws: a prefix's length. More... | |
| struct | dedekind::category::detail_comprehension::KeepsWhatItClassifies |
| struct | dedekind::category::WindowJoin< P, S > |
p >> s, SQL's JOIN on a window: the tensor of the windows of the words of p that land in s, at every offset where a window fits. A constructor, not a materialisation: the join is a tensor, counted, never enumerated, so it stays inside the counted world and never meets the bag a join yielding rows would need. More... | |
| struct | dedekind::category::Offsets< N > |
The offsets a window takes, as a tensor: the chain of m, its count m, one factor \(m^1\). Its outer product with a tensor is the copower \(m \cdot X\), m copies, the action of \(\mathbb{N}\) on the counts (:action; Listing 4's concat on copies). Fubini's sum over the offsets, every summand the same (period 1, translation invariance), is this factor of the product. More... | |
| struct | dedekind::category::JoinWith< P, S > |
join_with(p), a set of words to its join with p on the window: the join curried in the set, an arrow between tensors, its exponential transpose (curry, :cartesian); each join the pullback along the window (IsPullback, :pullback). More... | |
| struct | dedekind::category::Counting< J > |
counting, a counted tensor to its count: the reading that ends a pipeline of arrows, as count ends a query. The cardinality, strong monoidal from \(\otimes\) to \(\times\) (the box law read on counts), modulo the word: a ranking by it is a ranking of the exact counts only while they fit, which the law's plan, evaluating no count, does not need and the fused pass does (#1134). More... | |
| struct | dedekind::category::detail_comprehension::JoinWithStage< P > |
| struct | dedekind::category::detail_comprehension::CountingStage |
| struct | dedekind::category::detail_comprehension::Counted |
The count of a query, as Maybe's action: a query that may not be, counted where it is. More... | |
| struct | dedekind::category::antitone_on_even_lengths< Rank > |
A rank whose values do not grow along the even lengths, so that ranking the even lengths descending reads them upwards. Derived, not registered per rank: a family's occurrences in the words of length N are \((N - n + 1)\) offsets times its density times \(|\Sigma|^N\), a product of non-negative sequences that do not grow, which does not grow either. The product law, stated here for the join's exact count; a general one belongs in :posetal, on an exact carrier or on an extent, since a word's × wraps and an antitone product past it is not antitone (#1134). More... | |
| struct | dedekind::category::antitone_on_even_lengths< Compose< Compose< Fam, JoinWith< P, S > >, Counting< J > > > |
| struct | dedekind::category::OrderBy< Rank, Order > |
order_by(rank), SQL's ORDER BY: the stage that sorts a query by rank under Order, a strict weak order on the ranks. Ties are SQL's: which of the tied elements a take keeps, and in which order, is the plan's (a law's rewrite reads them in the walk's order or its reversal, the fused pass in the walk's); a rank without ties orders alike under every plan. More... | |
| struct | dedekind::category::Take< K > |
take<K>(), SQL's LIMIT: the first K present elements. More... | |
| struct | dedekind::category::detail_comprehension::ascends_by_position< F > |
The trait behind AscendsByPosition: an arrow over the positions whose present elements rise with the position, so that a query read through it is sorted by construction. A struct, so that a family registers by one partial specialisation. More... | |
| struct | dedekind::category::detail_comprehension::ascends_by_position< Where< P > > |
where keeps the positions it keeps: a filtered chain is a chain in the same order. More... | |
| struct | dedekind::category::detail_comprehension::One |
An element's share of a take: one if present, none for a hole, read through Maybe's eliminator. More... | |
| struct | dedekind::category::detail_comprehension::Taken< S, Coalg, K > |
The coalgebra of take<K>: the walk, until K elements are present. One more state, the count so far; O(1), no storage. More... | |
| struct | dedekind::category::detail_comprehension::element_of< E > |
The value a query's element carries: the Maybe's, or the element itself in a plain sequence. More... | |
| struct | dedekind::category::detail_comprehension::element_of< Maybe< T > > |
| struct | dedekind::category::detail_comprehension::Read< T, K > |
| The coalgebra reading a bounded list position by position, each element present: a value on the stack walked as any query, so every stage composes after it. More... | |
| struct | dedekind::category::detail_comprehension::Pending< Q, Rank, Order > |
order_by without a law to read, pending its take: it needs the whole query, so only the fused order_by >> take<K> is offered, never the sort alone (it would need the caller's buffer). More... | |
| struct | dedekind::category::detail_comprehension::Before< Rank, Order > |
Whether a ranks before b. More... | |
| struct | dedekind::category::detail_comprehension::KeepBest< T, K, Rank, Order > |
One more element into the best K so far, sorted by the rank: inserted after those it does not rank before, so ties keep the walk's order, and the last cut where the list overflows. O(K) on the stack. More... | |
| struct | dedekind::category::detail_comprehension::Progression< N > |
| The progression \(j \mapsto m j + r\), a residue class's members in order: a congruence filter on a chain read as a chain of its own. More... | |
| struct | dedekind::category::detail_comprehension::bound_of< Coalg > |
The most elements a walk presents: a take's K, or a bounded list's, kept by each SELECT and WHERE after it (a map presents no more than it reads). No member where nothing bounds the walk. More... | |
| struct | dedekind::category::detail_comprehension::bound_of< Taken< S, Coalg, K > > |
| struct | dedekind::category::detail_comprehension::bound_of< Read< T, K > > |
| struct | dedekind::category::detail_comprehension::bound_of< Mapped< S, Coalg, F > > |
Namespaces | |
| namespace | dedekind::category |
| namespace | dedekind::category::detail_comprehension |
Concepts | |
| concept | dedekind::category::detail_comprehension::IsMaybe |
| concept | dedekind::category::detail_comprehension::ReadsItsElements |
| A stage that reads a query's elements: an arrow out of the type its present elements have. | |
| concept | dedekind::category::IsQuery |
A query: a range whose elements are Maybe, each present or a hole. | |
| concept | dedekind::category::detail_comprehension::IsForeignView |
| A view of the caller's, not one of the library's walks. | |
| concept | dedekind::category::IsPrefixKernel |
| A kernel on the non-empty prefixes of a text: an arrow out of them, a coKleisli arrow of the history comonad, whose answer at a position may read all that came before it and nothing after. | |
| concept | dedekind::category::detail_comprehension::AscendsByPosition |
| concept | dedekind::category::detail_comprehension::KeptByTheWalk |
The walk's own order already is Order's under Rank: the chain descends by prior, so its elements fall along it, and a monotone rank under descending, or an antitone one under ascending, keeps them in order. order_by is then the identity. | |
| concept | dedekind::category::detail_comprehension::ReversedByTheWalk |
The walk's order is Order's reversed: order_by is the query re-rooted on the ascending slice, read upwards. | |
| concept | dedekind::category::detail_comprehension::IsBounded |
Typedefs | |
| using | dedekind::category::detail_comprehension::Evens = Subobject<std::size_t, Congruent<>> |
| using | dedekind::category::ascending = std::less<> |
The orders a query is sorted by, the standard's comparisons named as SQL's ASC and DESC: the defaults of order_by's parameter, which takes any comparison with sufficient structure, a total preorder on the rank's values. | |
| using | dedekind::category::descending = std::greater<> |
Functions | |
| template<std::semiregular Op> | |
| constexpr Lifted< Op > | dedekind::category::lifted (Op op) |
| template<IsArrow P> requires std::same_as<Cod<P>, bool> | |
| constexpr Where< P > | dedekind::category::where (P p) |
| template<detail_comprehension::IsForeignView R, IsArrow F> requires std::invocable<const F&, std::ranges::range_reference_t<R>> | |
| constexpr auto | dedekind::category::operator>> (R r, F f) |
r >> f on a range of the caller's: f on each element, which is std::views::transform. | |
| constexpr void | dedekind::category::detail_comprehension::refuse_window_width () |
Refuses a window of no letters, or wider than a range's distance type counts: the standard's slide has no such view. | |
|
template<std::ranges::view R> requires std::ranges::random_access_range<R> && std::ranges::sized_range<R> | |
| constexpr auto | dedekind::category::detail_comprehension::sliding (R r, std::size_t n) |
| constexpr Windows | dedekind::category::windows (std::size_t n) |
| template<detail_comprehension::IsForeignView R> requires std::ranges::forward_range<R> | |
| constexpr auto | dedekind::category::operator>> (R r, Windows w) |
| template<typename T, IsPrefixKernel< T > K> | |
| constexpr auto | dedekind::category::scan (K k, std::span< const T > s) |
scan(k, s), the extension of the history comonad: a kernel asked at every non-empty prefix, SQL's window function over ROWS UNBOUNDED PRECEDING, as scan on a Path (:sequences). Every position has a prefix, so unlike a window of n this is a comonad on a finite range; each answer may read the whole prefix, so a kernel that factors through finitely many states has an overload of its own (:automaton). | |
| constexpr bool | dedekind::category::detail_comprehension::lazy_is_the_functor () |
| template<typename P, typename S, IsArrow T> | |
| constexpr auto | dedekind::category::where (const Symmetric< P, S, T > &s) |
where(section): the windows a section holds, its membership as the filter, where(sites). The section stays a tensor (its * is the outer product); only here is it read as a predicate, its characteristic map, an arrow from a window of its letters: a std::span, since an arrow has one domain and a text is contiguous (a genome's bytes, Listing 5's carrier). The membership itself reads any random-access range; a window of a non-contiguous container is tested by it directly, not through this stage. | |
| template<detail_comprehension::IsForeignView R, typename P, typename S, IsArrow T> requires requires(R r, const Symmetric<P, S, T>& s) { std::move(r) >> windows(1uz) >> where(s); } | |
| constexpr auto | dedekind::category::operator>> (R r, const Symmetric< P, S, T > &s) |
r >> s: where the words of a section occur in a range of the caller's: its windows of the section's length, each kept where the section holds it. The section knows its length, so no window of another arises. Listing 5's dna >> pali6, the same expression as the join below read at one sequence. | |
| template<std::regular S, std::semiregular Coalg, IsArrow F> requires detail_comprehension::ReadsItsElements<unfold_view<S, Coalg>, F> | |
| constexpr auto | dedekind::category::operator>> (const unfold_view< S, Coalg > &q, F f) |
>> f on a query, SQL's SELECT: f on the present elements, the holes passed through, Maybe's action on the arrow (maybe_functor). | |
| template<std::regular S, std::semiregular Coalg, IsArrow F> requires detail_comprehension::ReadsItsElements<unfold_view<S, Coalg>, F> && detail_comprehension::IsMaybe<Cod<F>> | |
| constexpr auto | dedekind::category::operator>> (const unfold_view< S, Coalg > &q, F f) |
>> f on a query with f a Kleisli arrow into Maybe, a second where among them: Kleisli composition, f extended by Maybe's κ (:kleisli), so the holes do not nest. Filter fusion, where(p) >> where(q) = where(p ∧ q), is then the monad's associativity, not a rewrite of this partition's. | |
| template<IsCountWord N, typename P, typename Q> requires requires(const P& p, const Q& q) { { p & q } -> IsArrow; requires std::same_as<Cod<decltype(p& q)>, bool>; } | |
| constexpr auto | dedekind::category::operator>> (const FiniteChainSequence< N, Where< P > > &q, const Where< Q > &w) |
where(p) >> where(q) on a chain, fused by type: one where whose predicate is the lattice term p & q, the meet in the Boolean algebra of the chain's subobjects (:lattice's Meet). The filter is then a term the lattice's laws and the periodic counts read, not two walks; where p & q is not formed, the Kleisli composite above takes the query. | |
| template<IsCountWord N, HasPeriod P> requires std::same_as<Cod<P>, bool> | |
| constexpr N | dedekind::category::count (const FiniteChainSequence< N, Where< P > > &q) |
The count of a chain under a periodic filter: the count of the filter's characteristic sequence, which :periodic takes period by period (the lcm for a meet), no walk of the query. The law count(where(p)) = count(χ_p), used as the rewrite. | |
| template<IsCountWord N = std::size_t, IsQuery Q> | |
| constexpr N | dedekind::category::count (Q q) |
The count of a query, SQL's COUNT: its present elements, the tally lifted through Maybe. | |
| template<IsQuery Q, IsCountWord N = typename std::ranges::range_value_t< Q>::value_type> | |
| constexpr N | dedekind::category::sum (Q q) |
The sum of a query of counts, SQL's SUM: + lifted through Maybe. | |
| template<HasNNOStep C, IsArrow F, IsCountWord N, typename P, typename S, IsArrow T> requires requires(const Power<C, F, N>& p, const Symmetric<P, S, T>& s) { occurrences(p, s); } | |
| constexpr WindowJoin< Power< C, F, N >, Symmetric< P, S, T > > | dedekind::category::operator>> (const Power< C, F, N > &p, const Symmetric< P, S, T > &s) |
| template<IsCountWord N> | |
| constexpr AtMost< Factor< N >, 1 > | dedekind::category::count_factors (const Offsets< N > &o) |
| template<typename P, typename S> requires requires(const P& p, const S& s) { occurrences(p, s); } | |
| constexpr auto | dedekind::category::explain (const WindowJoin< P, S > &j) |
The plan of a join on the window, EXPLAIN: the offsets times the cylinder at the first, an outer product, the cylinder in turn its selection pushed down (pushed_down). Fubini, periodicity and the box law compose into it; none of the three is stated for joins. Its type is the plan, so a test asserts it. Defined where a window fits. | |
| template<typename P, typename S> requires requires(const P& p, const S& s) { occurrences(p, s); } | |
| constexpr auto | dedekind::category::count_factors (const WindowJoin< P, S > &j) |
| The factors of a join's count: its plan's, the box law throughout, with no sum over the offsets left; none where no window fits. | |
| template<typename P, typename S> requires requires(const P& p, const S& s) { occurrences(p, s); } | |
| constexpr auto | dedekind::category::count (const WindowJoin< P, S > &j) |
| template<typename P, typename S> requires requires(const P& p, const S& s) { occurrences(p, s); } | |
| constexpr auto | dedekind::category::exact_count (const WindowJoin< P, S > &j) |
| template<typename P> | |
| constexpr detail_comprehension::JoinWithStage< P > | dedekind::category::join_with (P p) |
| template<IsArrow Fam, typename P> requires requires(const P& p, const Cod<Fam>& s) { occurrences(p, s); } | |
| constexpr auto | dedekind::category::operator>> (const Fam &fam, const detail_comprehension::JoinWithStage< P > &st) |
| template<IsArrow F> requires requires(const Cod<F>& j) { count(j); } | |
| constexpr auto | dedekind::category::operator>> (const F &f, detail_comprehension::CountingStage) |
| template<IsQuery Q> | |
| constexpr std::size_t | dedekind::category::operator>> (Q q, detail_comprehension::CountingStage) |
q >> counting, the stage form of count(q): the fold that ends a query, in the query's own rhythm. | |
| template<IsQuery Q> | |
| constexpr Maybe< std::size_t > | dedekind::category::count (const Maybe< Q > &q) |
A query that may not be (a plan past its capacity), counted: the count lifted through Maybe, nothing where the query is nothing. The monad carries the uncertainty to the end, so no caller cases on it. | |
| template<IsQuery Q> | |
| constexpr Maybe< std::size_t > | dedekind::category::operator>> (const Maybe< Q > &q, detail_comprehension::CountingStage) |
| The stage form of the same. | |
| template<typename Order = ascending, IsArrow Rank> requires std::strict_weak_order<const Order&, Cod<Rank>, Cod<Rank>> | |
| constexpr OrderBy< Rank, Order > | dedekind::category::order_by (Rank rank, Order order={}) |
| template<std::size_t K> | |
| constexpr Take< K > | dedekind::category::take () |
| template<typename T> | |
| constexpr std::size_t | dedekind::category::detail_comprehension::present (const Maybe< T > &e) |
| template<typename T> | |
| constexpr std::size_t | dedekind::category::detail_comprehension::present (const T &) |
| template<std::semiregular T, std::size_t K> | |
| constexpr unfold_view< std::size_t, Read< T, K > > | dedekind::category::detail_comprehension::walk (const AtMost< T, K > &list) |
| template<IsCountWord N, typename F, IsArrow Rank, typename Order> requires std::same_as<Dom<Rank>, N> && detail_comprehension::AscendsByPosition<F> && detail_comprehension::KeptByTheWalk<Rank, Order> | |
| constexpr FiniteChainSequence< N, F > | dedekind::category::operator>> (const FiniteChainSequence< N, F > &q, const OrderBy< Rank, Order > &) |
order_by on a filtered chain whose rank keeps the walk's order: the identity, the query sorted by construction (rewrite 3). | |
| template<IsCountWord N, typename F, IsArrow Rank, typename Order> requires std::same_as<Dom<Rank>, N> && detail_comprehension::AscendsByPosition<F> && detail_comprehension::ReversedByTheWalk<Rank, Order> | |
| constexpr auto | dedekind::category::operator>> (const FiniteChainSequence< N, F > &q, const OrderBy< Rank, Order > &) |
order_by on a filtered chain whose rank reverses the walk's order: the query re-rooted on the ascending slice [0, n), read upwards, the positions where the walk read them downwards (rewrite 3). | |
| template<std::ranges::input_range Q, IsArrow Rank, typename Order> | |
| constexpr detail_comprehension::Pending< Q, Rank, Order > | dedekind::category::operator>> (Q q, OrderBy< Rank, Order > by) |
order_by with no law to read: pending its take. | |
| template<std::regular S, std::semiregular Coalg, std::size_t K> | |
| constexpr auto | dedekind::category::operator>> (const unfold_view< S, Coalg > &q, Take< K >) |
take<K> on a query or a sequence: the walk, until K elements are present, lazily (rewrite 1; on a sorted query, rewrite 3's first K). | |
| template<std::size_t K, typename Q, typename Rank, typename Order> | |
| constexpr auto | dedekind::category::detail_comprehension::fused_take (Pending< Q, Rank, Order > p) |
The fused plan of order_by >> take<K> with no law: one pass keeping the best K, the elements present. The pending query is owned, so a range iterable only when mutable folds too. | |
| template<IsArrow F, typename J, IsCountWord N> | |
| constexpr bool | dedekind::category::detail_comprehension::fits_the_word (const Compose< F, Counting< J > > &rank, N l) |
Whether a rank of lengths stays exact from length l on: its values do not grow there, so the largest is at l, and it fits the rank's word, the count's, where its factors say so (bits_needed): the product law's extent, decided by one count, before any is compared. | |
| template<typename Q, typename Rank, typename Order, std::size_t K> | |
| constexpr auto | dedekind::category::operator>> (detail_comprehension::Pending< Q, Rank, Order > p, Take< K >) |
order_by >> take<K> with no law: fused, one pass keeping the best K in an AtMost, never sorting the whole (rewrite 2). A query still, each element present, so its count is its rows' and collect makes the plain list. | |
| template<IsCountWord N, typename Rank, std::size_t K> requires std::same_as<Dom<Rank>, N> && antitone_on_even_lengths<Rank>::value | |
| constexpr auto | dedekind::category::operator>> (const detail_comprehension::Pending< unfold_view< std::optional< N >, detail_chain::SliceStep< N, Where< Congruent< N > > > >, Rank, descending > &p, Take< K >) |
The lengths in a residue class, ranked by their occurrences, the first K: one plan compiled, the even case, and the run time only reindexing into it. A family whose density falls along the even lengths ranks them by construction, the shortest first (the walk read upwards, rewrite 3), so the first K need no rank evaluated at all. A residue class of even modulus holds lengths of one parity: even ones are the case; odd ones occur nowhere (the reversal pairs the letters, and no even alphabet fixes them all), all ranked alike. Either way the class's members in order are the plan, a progression: padded into the class by \(j \mapsto m j + r\), unpadded by reading the lengths back. An odd modulus mixes the parities and takes the fused pass. | |
| template<std::regular S, std::semiregular Coalg> requires detail_comprehension::IsBounded<Coalg> | |
| constexpr auto | dedekind::category::collect (const unfold_view< S, Coalg > &q) |
A query's present elements as a value, bounded by its take: the ext boundary, where a list is asked for (#1133). A plain sequence's elements as they are, a query's unwrapped. | |
Variables | |
| template<typename T> | |
| constexpr bool | dedekind::category::detail_comprehension::is_maybe_v = false |
Whether T is a Maybe: an element of a query, or none. | |
| template<typename T> | |
| constexpr bool | dedekind::category::detail_comprehension::is_maybe_v< Maybe< T > > = true |
| template<typename R> | |
| constexpr bool | dedekind::category::detail_comprehension::is_walk_v = false |
| template<std::regular S, std::semiregular Coalg> | |
| constexpr bool | dedekind::category::detail_comprehension::is_walk_v< unfold_view< S, Coalg > > = true |
| constexpr std::array< std::size_t, 3 > | dedekind::category::detail_comprehension::history_witness {7, 8, 9} |
| constexpr std::array< std::size_t, 5 > | dedekind::category::detail_comprehension::functor_witness {0, 1, 2, 3, 4} |
| constexpr std::array< std::byte, 6 > | dedekind::category::detail_comprehension::gaattc |
| constexpr detail_comprehension::CountingStage | dedekind::category::counting {} |
| The stage counting what precedes it. | |
Monad comprehensions over a finite chain: the queries of SQL and LINQ, where, select, order_by and take, and the folds that end them.
@partition :comprehension
Every stage takes a query and returns a query, composed by >>, as Python's [f(x) for x in xs if p(x)] and SQL's SELECT ... WHERE do.
| LINQ | SQL | here |
|---|---|---|
from | FROM | n >> chain<N>() |
Select | SELECT | >> f |
Where | WHERE | >> where(p) |
Join | JOIN | p >> s |
OrderBy | ORDER BY | >> order_by(rank) |
Take | LIMIT | >> take<K>() |
| - | OVER | >> windows(n) |
Count | COUNT | count, >> counting |
Sum | SUM | sum |
The names are the common ones; where a name is the category theory's, the comprehension takes another: take, Haskell's and LINQ's, for SQL's LIMIT, since a limit here is the universal construction (:limit), and the monad's multiplication is μ; SQL's JOIN keeps no name of its own, being a power read through a set of words, the bind.
The carrier is a finite chain sequence holding at most one element per position: the list monad whose Kleisli arrows yield none or one, so a query is a chain read through a Kleisli arrow into Maybe. where is that arrow; select after it is Maybe's action on arrows; a fold ends the query, its step lifted once through Maybe's eliminator, so no consumer cases on a hole. A plain sequence has no holes and stays unlifted, by its type. The rewrites a query optimiser applies are laws of the monad and of the algebra: Wadler's comprehensions over any monad (1990), the comprehension syntax of Buneman, Libkin, Suciu, Tannen and Wong as a query language (1994), Grust's optimiser on its laws (#1133). A join yielding several rows per input leaves the carrier, a bag's; its count is the tensor products' (:periodic), never materialised.