ExpandCollapseNext Index

+ 1.1 Categorical Types.

Before we can venture into the arena of Felix generalised array support we need to review some basic category theory.

We need to understand two basic type constructors: products and sums. Products are well understood by all programmers. In math we speak of Cartesian Product such as R * R representing a plane in a Euclidean geometry, where R is of course the real number line.

In Felix, we just call these things tuples:

  var tup : string * int * double = "Hello", 42, 3.141;

using a chain of {*} for non-associative n-ary product type formation, and {,} for the corresponding value construction. An integer constant can be used to denote a projection function, which extracts one of the components.

  var c0 : string = 0 tup;
  var c1 : int = tup . 1;

The reverse application operator {.} makes the second form more intuitive. Projection functions are sometimes called fields. Such types are said to be structural because they do not have a specified unique name but are identified by their shape: Felix also has a nominally typed product, the usual C struct. The theory is simpler with structural typing so we will stick to that here.

Much less understood by programmers is the sum or variant type. Whilst a product may be thought of as "all of these things aggregated", a sum type is "pick one of these things". It is the type of choices, conditionals, and switched control flow.

The best known sum type is the humble enumeration, and the best understood enumeration is called bool. It is a type of two choices: false or true.

For structurally typed enumerations, we just use an integer constant to denote the type:

  begin
    var f : 2 = case 0 of 2; // aka false
    var t : 2 = case 1 of 2; // aka true
  end

which is identical to the more familiar:

  begin
    var f : bool = false; // aka false
    var t : bool = true; // aka true
  end

The case notation is a bit ugly, and it seems unnatural to number cases from 0 upto {n-1} with zero origin notation, but we have chosen that for consistency with tuple notation and C.

A more general sum type is shown below:

  var s : string + int + double = 
    (case 0 of string + int + double) "Hello"
  ;
  
  proc p(x: string + int + double) {
    match x with
    | case 0 x => println$ x+" world";
    | case 1 x => println$ x+1;
    | case 2 x => println$ x+2.2;
    endmatch;
  }
  
  p s;
  s = (case 1 of string + int + double) 42;
  p s;
  s = (case 2 of string + int + double) 3.141;
  p s;

Here you see three cases which carry data of a specific type. You use pattern matching to discover which of the three possible cases is carried and then safely access that type.

The case value thing is called a type constructor because it takes a value of one of the components of a sum type and creates a value of the sum type. This is the way to unify or homogenise heterogenous types. Note in the pattern matching we don't need to specify the type, only the case tag (index value).

Unlike products which are constructive types charactised by the projection functions that inspect them, sums are destructive types characterised by the injection functions that build them: namely the type constructors.