This section looks at Union Types. As we saw with Enumerations, there are times we want to represent different choices using the same type. Union types encode the idea that a type’s value can be one of many variants.
The name ‘union’ ostensibly comes from the C family of programming languages,
where unions were overlapping regions of memory where it was not clear which value you had.
Tagged unions
(also known as
variants
and
sum types)
support discrimination of these values using tags
i.e. constructor names.
Intersection types
on the otherhand are a special form of union.
These terms are often used interchangeably, do not do so!
Python
Python supports intersection types.
We show this by demonstrating an example of the Option type as a raw union.
def isJust(v : str | None):
match v:
case str():
print(f"Is String { v }")
case _:
print("Is none")
Here the bar (|) denotes the intersection.
When working with these values we have to match on their constructor to discriminate between the different cases.
We can also define a type synonym to make our functions easier to read.
type MaybeStr = str | None
def isJust(v : MaybeStr):
match v:
case str():
print(f"Is String { v }")
case _:
print("Is none")
Here type is the python keyword to declare a type alias
(synonym).
Dafny
Within Dafny,
we have tagged unions (aka variants).
And we use the same syntax as for records,
but use bar
(|)
to discriminate between the options.
datatype MaybeStr = Nothing | Just(value : string)
The syntax in Dafny for datatypes is the same as for records!
Remember that in functional programming, datatypes are actually known as: algebraic datatypes, where we can construct structured data as a sum of products. That is, a tagged union is the choice between a bunch or records which themselves can contain a bunch of sums…
Much like python we can use match to discriminate between the different choices.
function isJust(s : MaybeStr) : bool
{
match s
{
case Nothing => false
case Just(_) => true
// ^ Wildcard
}
}
Wildcards can be within the constructor pattern.
We can also write this function imperatively.
method IsJust(s : MaybeStr) returns (b : bool)
{
match s
{
case Nothing => return false;
case Just(_) => return true;
}
}
Idris
Idris like Dafny supports algebraic datatypes. Simple datatypes take an almost similar form:
data MaybeStr = Nothing | Just String
Remember that datatypes
in Idris are indexed families.
We can rewrite MaybeStr as:
data MaybeStr : Type where
Nothing : MaybeStr
Just : (value : String) -> MaybeStr
Again remember, the above definition is what the simple definition is transformed to internally!
We can then use pattern matching on function arguments to discriminate between the choices:
isJust : MaybeStr -> Bool
isJust Nothing = false
isJust (Just _) = true
Like Dafny Idris enables wildcards in constructors.
As we saw with
enumerations
we can also use an explicit case tree:
isJust : MaybeStr -> Bool
isJustr m
= case m of
Nothing => false
(Just _) => true