tl;dr
-
A value declaration must have a braced body, so a scalar can't be given a domain name
value LogOffset: Integer fails to parse. The only way to say "this Integer is a log offset" is a plain Integer field with a descriptive field name — which puts the name on the field rather than on the type, so nothing carries it to the next use site.
-
The one-field wrapper workaround asserts structure that isn't there
value LogOffset { offset: Integer } checks clean, but every use site reads through .offset, and the spec now claims LogOffset is a composite when it's a number.
-
The language reference already offers this and it doesn't exist
The primitive types section tells the reader to use "value types or plain String fields with descriptive names" for domain-specific string types. A value type needs at least one field, so that's a one-way choice dressed up as two.
-
What LogOffset is needs settling before the syntax does
Transparent alias or a distinct type (Q1) decides what the checker does with offset = 0 and with a.offset - b.offset.
Context
The everyday case for naming a type descriptively is a scalar, not a record: LogOffset, EmailAddress, Path, Millis, TenantId. Each is an Integer or a String that means something specific, and the meaning is exactly what a spec reader needs to see at the point of use.
Allium's value keyword is the natural home for that, but it's shaped for structured data only — the language reference introduces value types as "structured data without identity", with TimeRange and Location as the examples. There's no declaration form for the unstructured case.
Came up writing a spec with several distinct Integer-typed quantities in play, where offset: Integer and count: Integer sitting next to each other tell the reader nothing about which values are comparable with which — and there's no declaration form that can.
Current state
value requires a {:
-- allium: 3
value LogOffset: Integer
entity Log {
offset: LogOffset
}
error t.allium:3:16 expected '{', found ':'
error t.allium:6:13 allium.type.undefinedReference
Type reference 'LogOffset' is not declared locally or imported.
(allium 3.5.3)
The wrapper workaround passes, at the cost of a field the domain doesn't have:
value LogOffset {
offset: Integer
}
entity Log {
latest: LogOffset
is_empty: latest.offset = 0 -- .offset is pure ceremony
}
Open questions
-
Q1 — is a scalar value a transparent alias for its underlying type, or a distinct type?
Nominal is what makes the feature worth having: if LogOffset and MessageCount are both just Integer, the checker can't tell you you've compared one to the other, which was the point. But nominal has to answer what happens to arithmetic and comparison — whether LogOffset - LogOffset yields a LogOffset, an Integer, or an error, and whether the literal 0 is assignable to a LogOffset field.
-
Q2 — which type expressions are allowed on the right?
Just the primitives (value Millis: Integer), or the full field type grammar including compounds and optionals (value Offsets: List<Integer>, value MaybeName: String?).
-
Q3 — can a scalar value carry derived values or invariants?
A braced value type hangs them off its fields; a scalar has no field to name. If value Percentage: Decimal wants to constrain itself to 0..100, there's no established place to write it.
tl;dr
A
valuedeclaration must have a braced body, so a scalar can't be given a domain namevalue LogOffset: Integerfails to parse. The only way to say "this Integer is a log offset" is a plainIntegerfield with a descriptive field name — which puts the name on the field rather than on the type, so nothing carries it to the next use site.The one-field wrapper workaround asserts structure that isn't there
value LogOffset { offset: Integer }checks clean, but every use site reads through.offset, and the spec now claims LogOffset is a composite when it's a number.The language reference already offers this and it doesn't exist
The primitive types section tells the reader to use "value types or plain
Stringfields with descriptive names" for domain-specific string types. A value type needs at least one field, so that's a one-way choice dressed up as two.What
LogOffsetis needs settling before the syntax doesTransparent alias or a distinct type (Q1) decides what the checker does with
offset = 0and witha.offset - b.offset.Context
The everyday case for naming a type descriptively is a scalar, not a record:
LogOffset,EmailAddress,Path,Millis,TenantId. Each is an Integer or a String that means something specific, and the meaning is exactly what a spec reader needs to see at the point of use.Allium's
valuekeyword is the natural home for that, but it's shaped for structured data only — the language reference introduces value types as "structured data without identity", withTimeRangeandLocationas the examples. There's no declaration form for the unstructured case.Came up writing a spec with several distinct Integer-typed quantities in play, where
offset: Integerandcount: Integersitting next to each other tell the reader nothing about which values are comparable with which — and there's no declaration form that can.Current state
valuerequires a{:(allium 3.5.3)
The wrapper workaround passes, at the cost of a field the domain doesn't have:
Open questions
Q1 — is a scalar value a transparent alias for its underlying type, or a distinct type?
Nominal is what makes the feature worth having: if
LogOffsetandMessageCountare both justInteger, the checker can't tell you you've compared one to the other, which was the point. But nominal has to answer what happens to arithmetic and comparison — whetherLogOffset - LogOffsetyields aLogOffset, anInteger, or an error, and whether the literal0is assignable to aLogOffsetfield.Q2 — which type expressions are allowed on the right?
Just the primitives (
value Millis: Integer), or the full field type grammar including compounds and optionals (value Offsets: List<Integer>,value MaybeName: String?).Q3 — can a scalar value carry derived values or invariants?
A braced value type hangs them off its fields; a scalar has no field to name. If
value Percentage: Decimalwants to constrain itself to 0..100, there's no established place to write it.