CSE 428: Lecture 20. Additional lecture notes


Concrete datatypes

These additional notes are meant to provide some more examples about concrete datatypes. We have seen that the datatype declaration is used to introduce new types into SML. We do this by supplying the name of the type and the constructors for that type. For example, we can introduce a type for colors that contains three colors (constructors for colors).
   datatype color = red  |  blue  |  green;
This will declare the type color and three constuctors: red, blue, green. These constructors can now be used within patterns. For example,
   fun warmth red   = 1
     | warmth blue  = 2
     | warmth green = 0;
is a function that maps colors to integers.

For another example, consider the data type of numbers, which is composed of either integers or real numbers.

   datatype number  =  r of real  |  i of int;
Here there are two new constructors, These constructors can also be used in patterns. For example, the following function adds all the numbers in a list, returning the total as a real number. The function real coerces an integer to a real number.
   fun addnumbs(nil) = 0.0
     | addnumbs(i x :: k) = real(x) + addnumbs(k)
     | addnumbs(r x :: k) = x + addnumbs(k);
Entering the following line to the interpreter
   val x = addnumbs((i 1 :: r 5.4 :: i 9 :: nil));
returns
   val x = 15.4 : real
It is possible to also introduce polymorphic user datatypes, similar to lists. Consider the following declaration of binary trees that contains the empty tree and non-empty trees with nodes of type 'a:
   datatype 'a btree = emptybt | consbt of 'a * 'a btree * 'a btree;
Two new constructors are introduced, namely, Remember that 'a is a type variable and hence can be set to any other type and that btree is a postfix operator. For example,
   consbt(1, consbt(2,emptybt,emptybt), consbt(3,emptybt,emptybt))
denotes the tree
        1
       / \
      /   \
     2     3
while the expression
   consbt(1, consbt(2, emptybt,emptybt), 
             consbt(3, consbt(4,emptybt,emptybt), emptybt))
denotes the tree
        1
       / \
      /   \
     2     3
          /
         /
        4      
We can now write functions that can compute on binary trees. Forinstance, the following function height computes the height of a tree.
   fun max(x,y) = if x < y then y else x;

   fun height(emptybt) = 0
     | height(consbt(x,left,right)) = 1 + max(height(left), height(right));
The following function will add up the labels in the binary tree. Its type is int btree -> int.
   fun sum_labels(emptybt) = 0 
     | sum_labels(consbt(x,left,right)) = sum_labels(left) + x + sum_labels(right);

The following function takes a binary tree of integers and returns the binary tree with all the labels in it doubled.

   fun double(emptybt) = emptybt
     | double(consbt(x,left,right)) = consbt(2 * x, double(left), double(right));
All of the code in the above note is collected below. If you save the lines below into a file, say lnotes.sml, then type
   - use "lnotes.sml";
into the SML runtime system, that code will be loaded.
(* EXAMPLES OF DEFINITIONS AND USE OF DATATYPES *)

datatype color = red  |  blue  |  green;

fun warmth red   = 1
  | warmth blue  = 2
  | warmth green = 0;

datatype number  =  r of real  |  i of int;

fun addnumbs(nil) = 0.0
  | addnumbs(i x :: k) = real(x) + addnumbs(k)
  | addnumbs(r x :: k) = x + addnumbs(k);

val x = addnumbs((i 1 :: r 5.4 :: i 9 :: nil));

datatype 'a btree = emptybt | consbt of 'a * 'a btree * 'a btree;

val one = consbt(1, consbt(2,emptybt,emptybt), consbt(3,emptybt,emptybt));

val two = consbt(1, consbt(2,emptybt,emptybt), 
                    consbt(3, consbt(4,emptybt,emptybt), emptybt));

fun max(x,y) = if x < y then y else x;

fun height(emptybt) = 0
  | height(consbt(x,left,right)) = 1 + max(height(left), height(right));

fun sum_labels(emptybt) = 0 
  | sum_labels(consbt(x,left,right)) = sum_labels(left) + x + sum_labels(right);

fun double(emptybt) = emptybt
  | double(consbt(x,left,right)) = consbt(2 * x, double(left), double(right));