What is the variant data type?
Elara uses a strong, static type system, where the type of every value is defined in advance.
Variants add flexibility to the system, representing values that could be one type of thing or another.
Variants are known by many names – sum types, algebraic data types, tagged unions or discriminated unions are the most common (along with enum
in the Rust programming langauge).
Variant types consist of one or more cases. Each case has a name (or a tag) that describes which case it (similar to how fields within structs have names to describe each field). And each case may be associated with some auxilliary data describing that case. For the different cases, the type of the associated data can be different.
You can use variants to build a data model for your domain and to perform common tasks on Elara. Data models often include the possibility of an object being one thing or another, and variants are how you model that on Elara. Elara natively uses variants to represent optional data as well as patches.
Examples of variants
To gain a more grounded understanding of variants, consider the following examples.
Simple enumerations
Suppose you wanted to simulate a system involving traffic lights. The state of a traffic light could be red, yellow or green.
One could represent the state of traffic light as strings, like "red"
, "yellow"
or "green"
.
However, there is little guarantee that another string like "purple"
won't creep into your system.
You could attempt to use integers like 0
, 1
, and 2
to represent the three possible states.
Not only would you need to ensure other integers like 3
do not appear, it also is much less clear which state is which.
Many programming languages provide an "enumeration" data type to help with this situation.
In Elara you would use a variant with three cases .red
, .yellow
and .green
.
Variants allow for additional data to associated with each case, but here this is unnecessary.
The associated value is just null
.
With the EDK you can create variant values with the variant
function. For example:
const red = variant("red", null);
const yellow = variant("yellow", null);
const green = variant("green", null);
You can declare a variant data type containing any of these three cases using VariantType
.
const TrafficLightType = VariantType({
red: NullType,
yellow: NullType,
green: NullType,
})
Recall that NullType
that permits just the value null
.
We use that here to indicate that no associated data is necessary.
It is not possible for a value other than red
, yellow
or green
to be assigned to a datastream or a variable of type TrafficLightType
.
The EDK will detect any type errors before the Solution is deployed to Elara.
Guarantees like this can eliminate certain kinds of bugs from your code.
Augmenting with associated data
Note that for simulation purposes it might be useful to know how long the traffic light has been a certain color. For example, in a simulation you might know a yellow light stays yellow for 3 seconds before turning red.
One way of tracking that is to record when the the traffic light changed to this colour, as a date-time value. We could associate the date-time with each of the three cases. Thus, we might choose to use this type instead:
const TrafficLightType = VariantType({
red: DateTimeType,
yellow: DateTimeType,
green: DateTimeType,
})
If necessary, you can construct a value like variant("yellow", new Date("2023-11-20T02:45:13.554Z"))
for inclusion in your template.
Note that the type of associated data can differ between the cases. There are some examples of this below.
Optional data
One of the most widely used variant types is the "option" variant. It can be used to represent optional data. Data is optional when a value may or may not exist. This is an alternative approach to using nullable values to represent missing data, with some crucial differences.
The option variant has two cases.
It can either have some data or have none.
The cases are .some
and .none
.
The .some
case is associated with some data of a given type.
We can construct such a value in the EDK via variant("some", "name")
or with the provided shortcut some("name")
.
Elara might print that for example as .some "name"
.
The .none
case is not associated with any extra data.
We can construct such a value in the EDK via variant("none", null)
or with the provided constant none
.
Elara would print this as .none null
.
To declare the data type for optional strings you would use OptionType(StringType)
. This is just shorthand for:
VariantType({
some: StringType,
none: NullType,
})
Elara provides a variety of expressions for dealing with optional data. You can refer to the module on how to use variants in East expressions for more information on optional data
here.Nested associated data
You can use a struct to encapsulate multiple pieces of data associated with one of the cases. For an example, consider that you have geometric shapes in your data model. Suppose there are three types of objects – points, circles and rectangles. The type of data required to describe the size of each geometry (i.e. ignoring the location of its center) is:
- A
point
needs no data. - A
circle
is described by radius. - A
rectangle
is described by awidth
AND aheight
.
We can use a struct to hold both the width and height of a rectangle as fields. The East data type for to represent these geometries might be:
const GeometryType = VariantType({
point: NullType,
circle: FloatType,
rectangle: StructType({
width: FloatType,
height: FloatType,
}),
})
You can use this data modelling technique to model any situation where you may have objects of different kinds requiring different types of data to represent.
With expressions in Elara you can deconstruct values apply logic involving the associated data.
For example, we can compute the area of any one of the above geometries.
We can say that a point
has zero area.
The area of a circle
is πr².
And the area of the rectangle
is width
times height
.
Using the Match
expression to acheive this will be covered in a
Patches and edits
Elara has a
patch data source that allows you to perform small modifications to large collections. It does this by applying a patch which is a collection of edits. Each edit is an operation like "insert this entry" or "delete that entry".Suppose you wanted to patch a dictionary of type DictType(K, T)
.
For any given key, you can perform any of three possible edits.
If they key didn't exist prior to the patch, you could .Insert
it.
If the key did exist before the patch, you could .Update
the value or .Delete
it entirely.
The patch as a whole could be described by a type like:
DictType(K, StructType({
Insert: T,
Update: { inserted: T, deleted: T },
Delete: T,
}))
This data structure may appear odd a first. Patches in Elara are invertible and describe not only the new data, but also any data that has been deleted or modified. This allows Elara to more precisely detect and handle conflicts.
Next Steps
Now that you understand what a variant is, in the
next lesson you will deploy a variant data stream and practice reading and writing variant data.