diff --git a/docs/reference/language/declarations.md b/docs/reference/language/declarations.md index caca3045f..ad89f963a 100644 --- a/docs/reference/language/declarations.md +++ b/docs/reference/language/declarations.md @@ -20,7 +20,10 @@ characters rather than as a symbol. Write the thing rather than its symbol — ## `parameters` Declared shape only; the numbers bind by name at run time, in whatever -consumes the AST. +consumes the AST. What that binding may not decide for itself — where a +dimension's members come from, the order they stand in, and that a table +carries each coordinate at most once — is in +[dimensions](dimensions.md). ```yaml dimensions: diff --git a/docs/reference/language/dimensions.md b/docs/reference/language/dimensions.md index 8313c3107..106bd6878 100644 --- a/docs/reference/language/dimensions.md +++ b/docs/reference/language/dimensions.md @@ -32,8 +32,40 @@ in step by hand. **One master coordinate set per dimension, resolved before any data binds.** Every parameter is reindexed onto it, so two tables that disagree about which snapshots exist is an error at load time rather than a silently truncated -model. Where the coordinates come from, and in what order, is settled when -data is bound — which this package declares the shape of and does not do. +model. Which coordinates those are, and in what order they stand, is data's to +say — and the three rules below say how it says it. + +### Binding is the language's, even though the data is not + +The file declares an axis; the data supplies its members. Between those two +sentences sit three facts that decide **which model a file and a table make +together** — and a consumer answering any of them differently would build a +different model from the same two inputs. So they are the language's, and a +consumer implements them rather than choosing them. + +**The dimension's own source supplies its members.** They are read from the key +named after the dimension, and from nothing else: a parameter's table is read +for values, never for labels, and a lookup's map is not a claim about which +members exist. A dimension nothing supplies is an error naming it, not an empty +axis — an axis with no members would delete every row indexed by it, silently. + +**Their order is the order that source gives them**, first row first. It is not +sorted, and nothing about a label's type changes that: an axis of strings, of +integers and of timestamps are all read in the order they arrive. The order is +observable — [`shift`](operators.md#shift), `sum_back` and `position()` all walk +it — so a consumer that sorted would answer `shift(p, over=snapshot, offset=1)` +with a different row, and the file could not tell you which it meant. A model +wanting a particular order states it in the source it hands over. + +**One row per coordinate.** A parameter's table carries each coordinate of its +`dims` at most once, and a second row for one coordinate is an error naming the +coordinate — never a last-wins, a first-wins or a sum, each of which is a +defensible reading, which is exactly why the file may not leave the choice +open. A lookup's map obeys the same rule one axis over, and says so under +`lookups` below: it is single-valued per label of `over`. + +_At most_ once, rather than exactly once: a coordinate with no row is +[absence](absence.md), which is how a model masks. ## `lookups`