Value
A value is an expression, in the context of a statement, that provides a value to that statement. Values, statements and conditions work together to express business logic at an appropriate level of abstraction — detailed enough to be meaningful, but not so specific as to constrain implementation.
RIDDL 2.0 replaced what had been a mostly-opaque quoted string with a real value-expression system. A literal string is still accepted everywhere, so pseudo-code remains available where structure would be false precision.
The Eleven Forms¶
| Form | Syntax | Meaning |
|---|---|---|
| Literal | "some text" |
Opaque pseudo-code, or a literal constant |
| Empty | empty, none, empty Tags |
The minimum-cardinality inhabitant of a type — the absence of a value |
| Value reference | order.total |
A field, state field, function input, or let local |
| Constructor | OrderPlaced(id, total = x) |
Builds a message or record |
| Get | get from input SignupForm |
Reads a UI input or an entity state |
| Call | call function Pricing.Total(a, b) |
Invokes a pure function for its result |
| Prompt | prompt("compute the discount") [as <type>] |
A value computed by AI at generation time; the ascription states its type |
| Boolean | a > b and not c |
A structured boolean expression |
| Ask | ask query GetInfo of entity Catalog |
A query paired with the reply that answers it |
| Initiate | initiate entity Order |
Brings an instance into being; yields its Id |
| Self | self, self.id |
The instance executing right now |
Value References¶
A value reference names something in scope. Four sources are consulted, in order:
- the on clause's message binding — bare
ordis the whole message,ord.fieldreaches into it - a field of the handled message, the entity state, or a
function's
requiresinput - a definition reached by a qualified path, such as
GState.active - a named constant
A let local is also a value reference, but resolves lexically rather than
through the symbol table: it is visible only after its declaration and is
shadowed inside nested blocks.
Constructors¶
A constructor builds a message or record inline, rather than requiring it be assembled elsewhere first:
yield event OrderPlaced(orderId, total = cart.total, currency = "USD")
morph entity Order to state Done with record DoneData(note = "fulfilled")
Arguments are positional first, then named. Count, names, ordering and (best effort) types are all checked against the target's fields.
Get¶
get from reads a value from a UI input or an entity
state:
record BasketData is { total is Natural }
command AddItem is { sku is String }
entity Basket is {
state Filling of record BasketData is {
handler BasketHandler is {
on command AddItem {
let current = get from state Filling
}
}
}
}
The two forms cannot appear in the same clause. An input exists only inside
an application context, and state may be read only inside the entity that
owns it — reading another definition's state is an Error as of RIDDL 2.0.
Call¶
call invokes a function — and only a function, since
functions are the only definitions guaranteed pure — and produces its result:
A call is value-producing rather than a bare statement, so it composes with
let, set, return and constructor arguments. Calling something that
declares no returns is an Error.
Prompt¶
prompt("...") denotes a value computed by AI at generation time:
It is distinguished from the do statement by its parentheses. The
statement describes an action for a human to implement; the value denotes
something AI computes.
Multi-line prompts¶
Guidance long enough to need more than one line goes in braces, as a sequence of strings with no commas or other separator between them:
let sentiment = prompt({
"rate the customer's sentiment from the note on the order,"
"returning a value between 0.0 and 1.0"
}) as Real
The braces are the only multi-line form — a bare prompt("…") takes exactly
one string. That restriction is deliberate: RIDDL statements have no
terminator, so allowing two juxtaposed strings would leave nothing but the next
keyword to mark where the statement ended.
Typed holes: prompt("…") as <type>¶
prompt(...) may carry an optional type ascription:
That is a typed hole, and it is the seam between RIDDL's two tiers: the type is known and checkable at compile time, while the computation that produces a value of that type is prose an AI fills in at generation time. The deterministic tier parses, validates and type-checks; the AI tier is the string inside the parentheses.
The ascription restates the position's type; it never overrides it. A
let takes its type from the ascription, but in a position that already
determines the type — a constructor argument, a field — the ascription must
agree with it. Writing one is opt-in: unascribed prompt(...) is unchanged and
still valid, and is the right form wherever the position already says enough.
When written, the ascription is a type name. A bare predefined type such as
Real or TimeStamp is a name; String(1,30) or Recipe* is a type
expression, and ascribing one is the Error value-ascription-not-a-name —
declare the type with type and name it instead.
Empty and None¶
empty denotes the minimum-cardinality inhabitant of a type: no value at
all, written where a value is expected.
none is a synonym, not a second construct. Both spellings produce the
identical AST, and prettify converges them on empty. Use whichever reads
better where it appears — none often suits an optional scalar and empty a
collection — but expect formatted output to say empty.
Only where the minimum cardinality is zero¶
empty is meaningful only for a type that admits having no value: an optional
T?, a sequence T*, or an explicit range starting at zero, T{0,n}. A bare
T or a T+ requires at least one value, so empty is not an inhabitant of
it. A bare empty in such a position is the value-empty-not-allowed Error,
and ascribing such a type (below) is value-empty-needs-zero-cardinality. Type
names are read through, so empty fits a type Tags is String* just as it fits
String*.
Where its type comes from¶
Every value in RIDDL is typed. empty does not say which type in its own
spelling, so it takes the type of its position — the field it is assigned
to, a constructor or call argument, a let's declared type, an output, a
function's returns, a stored row, or the operand on the other side of a
comparison:
The type ascription¶
Where no position supplies a type, write one after empty. It must be a type
name, never a type expression:
Here Tags is String* and Nickname is String?, each declared with
type. Writing empty String* instead is the Error
value-ascription-not-a-name, and a bare empty with nothing to type it is
value-empty-untyped.
An ascription restates the position's type and never overrides it: where a
position does supply one, the ascription must be syntactically that declared
type, or it is value-empty-ascription-contradicts. A field declared inline,
like tags is String*, has no name to restate, so it takes only a bare empty.
Why the ascription cannot be followed by just anything
A type name is a bare path, and RIDDL statements are separated by
whitespace with no terminator. Without a guard, set x to empty followed
by set y to … would read the second statement's set as the first's
ascription, and put empty to … would read to. So the parser stops an
ascription at any word that can follow a value: a statement keyword, a
readability word such as to, in or where, or any reserved word that
cannot begin a type.
Boolean Expressions¶
Precedence runs or < and < not < comparison < atom, with parentheses to
group:
and, or, not, true and false are context-sensitive: they are
recognized only inside a boolean expression, so they remain legal identifiers
everywhere else in the language.
Comparisons are type-CHECKED, not reference-only
Either operand of a comparison may be any expression — a value
reference, a get from, a named constant, a literal,
arithmetic, a count of. What may sensibly be compared is validation's
question rather than the grammar's, so count > "5" is reported as the
Error value-ordering-needs-numeric instead of failing to parse.
A literal operand is legal and draws the style warning
value-literal-comparison-style. Naming the value is still better style,
because the name records the intent. The constant is a definition,
declared alongside the other definitions of its context:
and the comparison is a statement, written inside an on-clause:
The point is to remove magic constants from models and to make every
comparison check the types on both sides. == and != require operands
of the same category; <, >, <= and >= require an ordered numeric
type on both sides.
true and false remain valid boolean atoms — usable with and, or,
not, and standalone — just not as comparison operands.
Initiate¶
initiate brings a new instance of an entity into being, and its value is
that instance's identity:
It is not a second way to exist — construction still completes only when
on init finishes. But without it no Id value could ever come into being,
so nothing could be addressed.
initiate and terminate are entity-only and are both
effects: banned in a function body, in on activate/on passivate, and
in a correlation fold; legal in a saga step.
Discarding the id is a warning
The id is the only thing initiate produces, so binding it and never
using it means the instance was created and immediately made unreachable.
Self¶
self is the instance executing right now. Its type is a synthesized
record carrying id and version:
Because that type is an ordinary record, self.id resolves by the same path
walk as any other value — which is why no resolution rule has to know self
exists.
The type is not user-nameable, though: self.id is Id(Order) in an Order
handler and Id(Shipping) in a Shipping one. So let me: T = self has no T
to write, and self cannot be assigned into a message field — pass
self.id. The field set is closed; adding to it is a language change.
Where Values Are Used¶
| Statement | Operand |
|---|---|
set |
the assigned value |
let |
the bound expression |
put |
the published value |
return |
the returned value |
send, tell, yield |
a message reference or constructor |
morph |
a record reference or constructor |
when, require, invariant |
a condition |
match |
the subject, and each comparison pattern's comparand |
Occurs In¶
Contains¶
Values may contain other values — a constructor's arguments, a call's arguments, and a boolean expression's operands are all themselves values.