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 String* |
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.
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, and asking for one is the value-empty-needs-zero-cardinality Error.
The type ascription¶
empty may carry a type, which is what lets it be written in a position that
does not itself supply one — most often a constructor argument:
The ascription is where the cardinality rule is checked, so empty String —
a bare, one-or-more type — is the Error above, while empty String* is fine.
Why the ascription cannot be followed by just anything
A type expression 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. Every statement begins with a reserved keyword, so the parser
refuses those in the ascription position — a complete fix rather than a
heuristic, since no type can be named set.
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-safe and reference-only
Both operands of a comparison must be a typed reference — a value
reference, a get from, or a named constant — never a
literal. This is enforced at parse time, so count > 5,
count > "5", count > true and count > R(1) all fail to parse.
To compare against a fixed value, name it. 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.