1.1 Unions and Matches
The dual concept to structures is unions. A structure has "each of" logic whereas a union has "one of logic". Each-of logic demands treating each of the components in sequence whereas one-of logic demand selecting one of the components.
Unions therefore represent alternatives. We use pattern matching with
the match
construction to discover which alternative is encoded
and handle it in a typesafe manner. A match
is an advanced form
of a switch
: union
s are an advanced form of an enum
eration.
variant boolean = | True | False ; var b:boolean = True; proc f(b:boolean) { match b with | #True => println "True"; | #False => println "False"; endmatch; } println$ match b with | #True => "True" | #False => "False" endmatch;
This code demonstrates a simple union of two cases: True
and False
and shows a procedural match
whose case handlers are println
statements, and a function match
whose handlers are expressions.
A more sophisticated example:
variant Z = | Cart of double * double // cartesian complex x+iy | Polar of double * double // polar complex re^(i\theta) ; fun str (z:Z) => match z with | Cart (x,y) => str x + "+"+str y+"i" | Polar (r,theta) => str r + "e^"+str theta+"i" endmatch ;
The identifiers Cart
and Polar
here are called type constructors
or just plain constructors, note here the arguments of the constructors
are both the same: a pair of double
.
The question mark {?} here is used to introduce a parameter to represent the argument.
Unions are the way to unify heterogenous data types. If you're from an OO background you may think subclassing should be used for this. Unlearn this error. It is utterly wrong.
Subclassing is a way to represent a subtype, that is, a type with a subset of values of another type: for example the set of triangular matrices is a subtype of all matrices.
Similarly, composition using tuple or structs is the way to add more data to a type. Not inheritance. Unlearn that mistake too!
The match construction is very powerful. It can be used to match tuples and records too:
fun fst (a:int * long) => match a with | x,_ => x endmatch ; fun xaxis (a:(x:double, y:double))=> match a with | (x=xval) => xval endmatch ; println$ fst (41,99l); println$ xaxis (x=1.2,y=2.2);
Here the _
is a wildcard that matches anything.
You can also match against literals and ranges of integers floats and strings:
fun matcher (x:int) => match x with | 1 => "one" | 2..10 => "two to ten" | _ => "other" endmatch ;
Of course, matches are general patterns and can be combined, we've already seen that in the first example which combines are match against constructors and tuples.
1.1.1 Enumerations
Felix has C++ style enum
s.
1.1.1.1 Linear enumeration
enum colour {red, green, blue}; instance Str[colour] { fun str: colour -> string = | #red => "red" | #blue => "blue" | #green => "green" ; } println$ red;
This is a shorthand for a union, where the enumeration tags are type constuctors.
Note also in the above example the shorthand version of a function which consists solely of a match.
1.1.1.2 Flags
Here's an example with user specified constructor indicies:
enum flags {b1=1,b2=2,b3=4}; println$ caseno b3; // Use C to model flag like operations type flagset = "int"; ctor flagset: flags = "$1"; fun \(\vee\) :flags * flags -> flagset = "$1|$2"; fun \(\vee\) :flags * flagset -> flagset = "$1|$2"; fun \(\vee\) :flagset * flags -> flagset = "$1|$2"; fun \(\cup\) :flagset * flagset -> flagset = "$1|$2"; fun \(\in\) : flags * flagset -> bool = "($1&$2)!=0"; fun ==: flagset * flagset -> bool = "$1==$2"; fun !=: flagset * flagset -> bool = "$1!=$2"; fun \(\subseteq\) : flagset * flagset -> bool = "($1&~$2)==0"; fun \(\subset\) (x:flagset,y:flagset)=> x \(\subseteq\) y and not x == y; fun \(\supset\) (x:flagset,y:flagset)=> not x \(\subseteq\) y; fun \(\supseteq\) (x:flagset,y:flagset)=> not x \(\subset\) y; var x = b1 \(\vee\) b2; // infix operator \| is function bor! println$ b1 \(\in\) x; // true: infix operator in is function mem! println$ b2 \(\in\) x; // true println$ b3 \(\in\) x; // false println$ b1.flagset \(\subset\) x; // true println$ x \(\subset\) b1.flagset; // false
You can find the index
of any constructor as an int
with the caseno
operator.
In the example note that although the constructor with index
0 and 3 is not named, it is still a value of the type.
You cannot directly perform mathematical or bitwise operations
on constructors, not even enumeration constants. Note that that includes
comparison for equality! Use the caseno
operator to get around this,
or bind into C as shown in the example.