Four unrelated-looking parts of Mica turn out to be one idea:
for day := Monday to Friday do … { counting }
case day of Monday: … end; { dispatching }
booked[Friday] := 4; { indexing }
if day in busy then … { membership }Every one of them works over Weekday, an enumeration — and over integers,
characters, booleans, subranges and error codes too. None of them works over a
float64. The reason is a single property those types have and floats do not:
their values have a position.
Mica calls that property ordinal, and the rules are written against it rather
than against any list of type names. That is why learning it once explains all
four constructs, and why the diagnostics name a capability instead of a type.
The example is
examples/Ordinals.
What “has a position” means
An ordinal type is a discrete, ordered domain: the values line up, each one sits at a numbered place, and there is a next one and a previous one. Three verbs make the position visible:
WriteLn("Ord(Monday) %lld", Ord(Monday)); { 0 }
WriteLn("Ord(Friday) %lld", Ord(Friday)); { 4 }
WriteLn("Ord('A') %lld", Ord(letter)); { 65 }
WriteLn("Ord(True) %lld", Ord(True)); { 1 }
WriteLn("Succ(Monday) %lld", Ord(Succ(Monday)));{ 1 }
WriteLn("Pred(Friday) %lld", Ord(Pred(Friday)));{ 3 }Ord answers where a value sits. Succ and Pred step one place. They read
identically on an enumeration and on a character, because both are ordinal for
the same reason — a character’s position is its code point.
A float has none of this. What is the successor of 2.5? There is no answer
that is a value of the type rather than an artefact of its precision, so a float
carries numeric and ordered but not ordinal.
Counting
for day := Monday to Friday do
total := total + Ord(day);A for loop walks a domain from one position to another, so its control
variable must have positions. Try it with a float and the rule names itself:
for statement control variable requires a supported ordinal data type, found float64Note what the message does not say. It does not list the accepted types — it names the capability, because the accepted set is “everything carrying it”, and that set grows whenever you declare a new enumeration.
Indexing — the part most readers do not expect
An array index maps a value to a slot, which is again a position. So a fixed array can be indexed by any ordinal type, not only by integers:
type
Weekday = (Monday, Tuesday, Wednesday, Thursday, Friday);
Hours = array[Monday..Friday] of int64;
var
booked : Hours;
begin
booked[Monday] := 3;
booked[Friday] := 4;There is no integer anywhere in that declaration, and no conversion at the use
site. The array has one slot per named day, and booked[Friday] is a
compile-time offset exactly as booked[4] would be — with the difference that
nothing in the program has to remember that Friday is 4.
This is the shape to reach for whenever a table is indexed by a fixed set of named things: the enumeration is the index type.
Dispatching
case day of
Monday:
WriteLn("Monday the week starts")
Wednesday:
WriteLn("Wednesday the middle")
Friday:
WriteLn("Friday the week ends")
else
WriteLn("a quiet day no label of its own")
end;A case labels values of a discrete domain, so the selector must be ordinal.
The compiler computes the labels’ positions and dispatches on them — which is
also how it knows two labels do not overlap and that a label lies inside the
selector’s type.
Membership
type
Days = set of Weekday;
var
busy : Days;
begin
busy := [Monday, Wednesday, Friday];
if day in busy then
total := total + 1;A set stores membership over a domain by giving each position a bit, so its
element type must be ordinal too — set of Weekday is five bits, and
day in busy is one bit test.
Subranges: an interval that stays ordinal
type
WorkHour = 8..17;
var
hour : WorkHour;
begin
for hour := 8 to 17 do …A subrange restricts an ordinal base type to a closed interval and remains ordinal itself, so it counts, dispatches, indexes and joins sets like its base — while telling the reader, and the compiler, that 3 is not a work hour.
The whole idea in one table
| Construct | Why it needs positions |
|---|---|
for i := lo to hi | walking a domain means stepping from one position to the next |
case x of | dispatching means labelling discrete values |
a[i] | indexing means mapping a value to a slot |
set of T | membership means one bit per position |
| Ordinal | Not ordinal |
|---|---|
int8…int64, uint8…uint64 | float32, float64 |
bool, unicode | string, stringbuffer |
| enumerations, subranges | records, arrays |
| error domain codes | int128 and wider — see below |
Two boundaries worth knowing
Wide integers are deliberately not ordinal. int128 is numeric,
ordered and integral, but the ordinal machinery computes positions in an
int64 domain that a wide value cannot enter — so a wide integer does
arithmetic and comparison, and does not index, dispatch, or drive a for.
Error domain codes are ordinal but not ordered. A code has a position, so it
can be dispatched on with case; but < between two error codes is refused,
because codes are categories rather than magnitudes.
Try it
- Add
SaturdaytoWeekdayand rebuild without touching anything else. It compiles:set of Weekdaywidens by itself, because its width is the domain. Theforbound and the array’s bounds do not follow, because you wrote those intervals explicitly — changeHourstoarray[Monday..Saturday]and the new day gets its slot. Which parts follow and which are your own statement is worth knowing before you rely on either. - Index
bookedwithOrd(day)instead ofday. It fails: the array’s index type isWeekday, and anint64is a different domain, not a looser one. - Uncomment the float loop at the end of the example and read the diagnostic once more, now that you know it is naming a capability.
Next
The capability vocabulary these rules read — ordinal, plain, numeric and
the rest — is listed whole in the type system reference.