Variants
Variants
A variant
is a type that acts like a union
of each type that declares itself a variant-member
.
A variant is open-ended, somewhat like an abstract class
in other languages.
Or you could think of it as a union that's been scattered into separate declarations.
variant-member
is not a subtype;
the variant type and member type variant need explicit conversion.
It's also not inheritance. Making a type a variant-member
doesn't alter the type;
it alters the variant to have a new member.
variant-member
parses like any other modifier and can be mixed with them.
A type can have any number of variant-member
declarations.
So you could write: r record(xs nat mut[]) mut, foo variant-member, bar variant-member
.
Each variant-member
generates these functions:
- A
to
function for converting to the variant. - A function with the type's name that converts back from the variant to an option of the type.
-
For records, a function with the type's name that constructs it as a variant directly.
(So you can write
10.crow
instead of(10,)::crow.to
.)
Methods
Since variants are open-ended, it's not possible to handle all members in a match
.
If you do want to make sure every member implements something, the variant can declare methods.
Then each variant-member
must appear in a scope
where the method is implemented for the member type.
Methods share the same syntax as spec
signatures.
Each method declared generates a function for calling it, where the variant is the first parameter.
Each method implementation should have the variant-memmber
type as the first parameter.
Methods can take any number of parameters. The variant is always inserted at the front.
Variants vs unions
When in doubt, you should use a union.
Since all members of the union are known, you can match
on it
without needing an else
branch.
Since you can convert from a variant to its member types, it's not the best option for an interface that is supposed to be only accessed through its methods. For that, instead use Lambdas.
The advantage of a variant is extensibility. For example, the code defining exception
doesn't need to know every exception that might exist, so it's a variant
to allow other code to define exceptions.
In the case of exceptions, we do want to be able to convert to the particular exception type,
and not just rely on an interface common to all exceptions.