How the language is organized¶
A request is a list of tags. Each tag answers a question about the data, and the questions fall into four groups, told apart by one test: what does leaving out a tag you need cause?
Group |
Leaving out a tag you need |
For example |
|---|---|---|
does not compile: the operation does not exist |
|
|
is a bug: the program relies on something nobody keeps |
|
|
is slower by a complexity class |
|
|
is slower by a constant factor, and never changes a guarantee |
|
The test is about leaving a tag out. Asking for a tag you do not need only costs speed, whatever its group. Within the groups:
A category is one question, such as Which keys? (semantics). A request answers each category at most once:
denseandsparseboth answer Which keys?, so a request holds one of them. A category left unanswered stays at its none, which promises nothing: urd may pick anything there. In code a category is an enum, none first (urd::keys { none, dense, sparse }), and each answer has a tag type (urd::dense). Categories are open: another enum with tag types is another category (Extending urd).A kind answers a set of categories, its request format: a queue has no
bidirectional, a set nofifo. A tag outside the format of the request’s kind does not compile (a property means nothing for this kind of Store). Each category’s page lists the kinds that answer it.A tag is one answer to a category. Within a category some answers promise more than others:
constant_clear(clear in O(1)) is at leastproportional_clear(clear in O(elements present)), so a container that clears in O(1) also serves a request forproportional_clear. Each category’s diagram draws this: an arrow points to an answer that promises at least as much.
Take Distance from the example on the front page:
using Distance = Store<map<User, int>, dense, constant_clear>;
Group |
Category |
Answer, and what leaving it out would cause |
|---|---|---|
Kind |
(the first argument) |
|
Semantics |
Which keys? |
|
Costs |
How is it cleared? |
|
Hints |
(none) |
nothing to say about the workload |
Every other category |
(unanswered) |
its none: no promise |
urd picks the cheapest container that keeps every answer: here a map with a generation counter per slot
(generation_map). A Store has the properties it was asked for, not those of its representation: a set that
reclaims resolves to a tree, which is sorted, but was not asked to be, so it is not.
Who keeps an answer¶
Tags also differ in who makes the promise, shown on each tag’s card. The role follows from the group, and in semantics from who keeps the tag:
a kind makes the Store a set, a queue, a pool or a partition, named first (
queue<V>), and its tags say how (fifo);a guarantee is kept by the container (
sorted,stable_refs, and the costs); a container declares its guarantees in its declaration’sprovides(Representations), and the typed tests check them against a reference model;a demand is a promise you make about the data (
dense: the keys really are indices);a request only a request states, and the Store’s interface enforces it (
frozen,positional);a hint (
small) changes the choice, never a guarantee.
A request is what it states, and what that means (each kind’s meaning, include/urd/kinds.hpp): a set, and a
map without multi, keep each key once; a positional set numbers its keys as they come; a frozen Store’s
references are stable (any_kind::meaning, whatever the kind).
Tags are what a request states. What its data is (whether a key can be hashed, is an index, copies as bytes, …)
is not stated but derived from the types: these are type traits (urd::trait::can_hash, …,
include/urd/registry/facts.hpp), which containers rely on by role (the elements, the order, a map’s values, the
rows). They never appear in a request; see Elements: identity, hashing, order, indices for the concepts they are derived from. What the Store’s interface offers follows from frozen, monotone, monotone_until_clear, nonempty and
fixed_shape. Each container declares everything it guarantees, and what it relies on: one that never erases relies
on monotone, one built once on frozen.
The groups¶
Group |
Leaving out a tag you need |
Questions |
Tags |
|---|---|---|---|
Leaving it out does not compile: the operation does not exist. |
How are elements taken out? · Reached by handle? · Groups of keys? · Freed slots kept by a key? · Is room made ahead? |
|
|
Leaving it out is a bug: the program relies on something nobody keeps. |
Which keys? · How do priorities move? · What compares the elements? · In which order is it iterated? · Reached by position? · Duplicates allowed? · Values per key? · Does it change after building? · Can it be empty? · Shared between threads? · Do references stay valid? · Same contents, same bytes? · Nested scopes? · Layered over a parent? · Looked up both ways? · Rows of a fixed shape? · One array? · Records packed flat? · Elements stored whole, or in fewer bits? |
|
|
Leaving it out makes it slower by a complexity class: what is guaranteed is how much work or memory. |
How is it cleared? · What happens to freed memory? · Rows share structure? |
|
|
Leaving it out makes it slower by a constant factor; it never changes a guarantee. |
How many elements? |
|
Each group’s page holds its categories, and each category its tags. To start from a need in plain words instead, see Index by need.
Order by a comparator¶
order_by<C> is a tag that carries a comparator C: it orders a sorted Store or a by_priority queue by
C instead of urd::Less, as std::set’s comparator does. C compares two elements, in a map two
keys. urd::by compares by a projection: order_by<by<&Job::deadline>>. The order is C and nothing
else: elements C does not separate are equivalent, so a unique Store keeps one of them. See
urd::order_by.
keyed<K> likewise carries a key type K: a Store of slots<T> acquires its slots by key, and reuses a slot
given back only under an equal key (Pools).
Incompatible combinations¶
A request is one of three things: served, missing, or incompatible. What a request resolves to shows the first two for a
grid of requests (a none row carries a note saying what is missing). to_spec rejects only what is
ill-formed whatever container would hold it: a first argument that is no kind, an argument after it that is no tag,
a tag its kind’s format does not ask, and the rules its kind states (well_formed in include/urd/kinds.hpp: a
queue is reached by fifo, lifo or by_priority, monotone_priority needs by_priority, order_by
needs sorted or by_priority), each with its message. A compile-fail case in tests/compile_fail_store.cpp pins
each message (urd_compile_fail_expects in CMakeLists.txt).
Request |
Why |
Instead |
Case |
|---|---|---|---|
A first argument that is no kind: |
The kind comes first, with its element types. |
|
8 |
An argument after the kind that is no tag |
Every argument after the kind is a tag, |
Move the type into the kind: |
9 |
Two tags of one category: |
One question, two answers. |
Keep one. |
4, 5, 13 |
|
Handles are a pool’s (or a queue’s); a set reaches its keys by themselves. |
|
24 |
|
A partition groups keys, each once. |
A |
39 |
|
|
Add |
25 |
|
|
Fix |
27 |
Every other request no container serves is rejected by the registry, with one message: “no container serves this request: drop or change a tag”. Some examples:
Request |
Why |
Instead |
Case |
|---|---|---|---|
|
Contiguous storage moves its elements when it grows. |
Add |
6 |
|
No concurrent sorted container is registered. |
Drop |
41 |
|
Dense keys are indices; a string is none. |
A key with |
7 |
|
An unordered Store iterates in an order its contents do not fix, so it has no hash. |
|
28, 29 |
|
Slots are recycled; nothing continues their handles over a parent. |
|
36 |
|
A tree pool numbers nodes, not rows; no layer continues its handles. |
Drop |
37 |
|
A tree pool hands out its own handles (a root and a size), not the index the request names. |
Drop |
38 |
|
The concurrent tables (gtl) return their storage on |
Drop |
grid |