ExpandCollapseNext Index

+ 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: unions are an advanced form of an enumeration.

  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 enums.

+ 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.