Records with named fields can be defined as a series of
    name=value pairs
- val war = { country="Iraq", wmd=false, oil=true };
> val war = {country = "Iraq", oil = true, wmd = false} :
  {country : string, oil : bool, wmd : bool}
    Tuples are just records with numbered field names
- val tup = { 1="hi", 3=16, 2=true };
> val tup = ("hi", true, 16) : string * bool * int
    Fields can be accessed using
    #field record
- #wmd war; > val it = false : bool - #2 tup; > val it = true : bool
Fields can also be given names using pattern matching
- val { country=target, oil=attack, wmd=_ } = war;
> val target = "Iraq" : string
  val attack = true : bool
    It is okay to select a subset of fields
- val { country=target, oil=attack, ...} = war;
> val target = "Iraq" : string
  val attack = true : bool
    As a shortcut, you can use the field name as the value name
- val { country, oil, ... } = war;
> val country = "Iraq" : string
  val oil = true : bool
A type can be given a name
- type employee = { name: string, salary: int, age: int}; 
> type employee = {age : int, name : string, salary : int}
- fun tax (e: employee) =
      real (#salary e) * 0.22;
> val tax = fn : {age : int, name : string, salary : int} -> real
- fun tax ({salary,...}: employee) =
      real (salary) * 0.22;
> val tax = fn : {age : int, name : string, salary : int} -> real
Consider the King and his court:
datatype degree = Duke
                | Marquis
                | Earl
                | Viscount
                | Baron
datatype person = King
                | Peer of degree * string * int
                | Knight of string
                | Peasant of string
    This defines a series of constructors for each type. To create an instance of the type, use the constructor
- val me = Peasant "Russ"; > val me = Peasant "Russ" : person
Constructors must be unique.
All the variants are part of a single type
- [King, Peer (Duke, "Gloucester", 5), Knight "Gawain", Peasant "Jack Cade"]; > val it = … : person list
Pattern matching uses the same constructors
- fun superior (King, Peer _) = true
    | superior (King, Knight _) = true
    | superior (King, Peasant _) = true 
    | superior (Peer _, Knight _) = true
    | superior (Peer _, Peasant _) = true
    | superior (Knight _, Peasant _) = true 
    | superior _ = false;
> val superior = fn : person * person -> bool
Exceptions are raised on various runtime failures including failed pattern match, overflow, out-of-memory error, etc. You can also define custom exceptions and raise them explicitly.
- exception Failure; > exn Failure = Failure : exn - exception Bad of int; > exn Bad = fn : int -> exn - raise Failure; > Uncaught exception: ! Failure - raise (Bad 10); > Uncaught exception: ! Bad
Exception handlers take the form
        E handle P1 => E1
        | … |
        Pn => En
    Datatype definitions can be recursive, including polymorphic datatypes. We can define a binary search tree as
datatype 'a tree = Lf | Br of 'a * 'a tree * 'a tree;
…
- Br("little", Br("three", Lf, Lf), Br("pigs", Lf, Lf));
> val it = Br("little", Br("three", Lf, Lf), Br("pigs", Lf, Lf)) :
string tree
    The standard list is no different from a regular datatype
infixr 5 :: datatype 'a list = nil | op:: of 'a * 'a list;
Binary search trees are a simple way to represent sets. When
    balanced, they offer
    O(n log n) runtime for all
    basic operations. A binary search tree is a binary tree where
    data elements are held in the interior nodes (not in the
    leaves), and the element in a node is greater than all elements
    in the left subtree and less than all elements in the right
    subtree.
lookup
- fun lookup Lf (_:string) = false
    | lookup (Br (elt, left, right)) x =
        if x < elt then lookup left x
        else if x > elt then lookup right x
        else true;
> val lookup = fn : string tree -> string -> bool
    To insert a new value, we search for the proper place, insert the new value, and copy every branch on the path to the new value
- fun insert Lf (x:string) = Br(x, Lf, Lf) 
    | insert (Br (elt, left, right)) x =
        if x < elt then Br (elt, insert left x, right)
        else if x > elt then Br (elt, left, insert right x)
        else Br (elt, left, right);
> val insert = fn : string tree -> string -> string tree
To avoid copying anything when as existing element is inserted, we can use exceptions
- exception Duplicate;
> exn Duplicate = Duplicate : exn
- fun insert Lf (x:string) = Br(x, Lf, Lf)
    | insert (tree as Br (elt, left, right)) x =
        (if x < elt then Br (elt, insert left x, right)
        else if x > elt then Br (elt, left, insert right x)
        else raise Duplicate)
      handle Duplicate => tree;
> val insert = fn : string tree -> string -> string tree
    This version raises an exception and catches it at every level of the tree. It would be better to use only one exception to go all the way back to the top level
- fun insert tree (x:string) =
    let fun ins Lf x = Br(x, Lf, Lf)
          | ins (Br (elt, left, right)) x =
              if x < elt then Br (elt, ins left x, right)
              else if x > elt then Br (elt, left, ins right x)
              else raise Duplicate
    in ins tree x end
    handle Duplicate => tree;
> val insert = fn : string tree -> string -> string tree
We can write recursive functions for trees
- fun count Lf = 0
    | count (Br (_, l, r)) = 1 + count l + count r;
> val 'a count = fn : 'a tree -> int
- fun depth Lf = 0
    | depth (Br (_, l, r)) = 1 + Int.max(depth l, depth r);
> val 'a depth = fn : 'a tree -> int
- fun inorder Lf = []
    | inorder (Br (v, l, r)) = inorder l @ v :: inorder r;
> val 'a inorder = fn : 'a tree -> 'a list
- fun inorder tree =
    let fun f Lf a = a
          | f (Br (v, l, r)) a = f l (v :: f r a)
    in f tree []
    end;
> val 'a inorder = fn : 'a tree -> 'a list
    We can insert all the elements from a list into a tree
- fun list2tree lst = foldl (fn (a,b) => insert b a) Lf lst;
If the list is in order, the resulting tree will effectively be a list as well.
Some familiar functionals have natural analogues in trees
- fun maptree f Lf = Lf
    | maptree f (Br (v, l, r)) =
        Br (f v, maptree f l, maptree f r);
> val ('a, 'b) maptree = fn : ('a -> 'b) -> 'a tree -> 'b tree
- fun fold f e Lf = e
    | fold f e (Br (v, l, r)) =
        f (v, fold f e l, fold f e r);
> val ('a, 'b) fold = fn :
    ('a * 'b * 'b -> 'b) -> 'b -> 'a tree -> 'b
    As with lists, many simple tree functions can be rewritten using functionals
- fun count t = fold (fn (_, l, r) => 1 + l + r) 0 t; > val 'a count = fn : 'a tree -> int - fun depth t = fold (fn (_, l, r) => 1 + Int.max(l, r)) 0 t; > val 'a depth = fn : 'a tree -> int
We can also write fold functionals that let us treat an ordered binary tree as a list
- fun foldltree f e Lf = e
    | foldltree f e (Br (v, l, r)) =
        foldltree f (f (v, foldltree f e l)) r;
> val ('a, 'b) foldltree = fn :
    ('a * 'b -> 'b) -> 'b -> 'a tree -> 'b
- fun foldrtree f e Lf = e
    | foldrtree f e (Br (v, l, r)) =
        foldrtree f (f (v, foldrtree f e r)) l;
> val ('a, 'b) foldrtree = fn :
    ('a * 'b -> 'b) -> 'b -> 'a tree -> 'b
    This gives us an easy way to collect the elements from a tree into a list
- fun tolist t = foldrtree op:: [] t; > val 'a tolist = fn : 'a tree -> 'a list
Using foldltree would give us the elements in reverse
    order. We could have done this with the normal tree fold, too, though
    not as efficiently
- fun tolist t = fold (fn (v, l, r) => l @ v :: r) [] t; > val 'a tolist = fn : 'a tree -> 'a list
A binary search tree can be extended easily to act as a dictionary mapping keys to values
- datatype ('a, 'b) dict = Lf | Br of 'a * 'b * 'a dict * 'a dict;
    lookup should return a value or raise an
    appropriate exception if the key is not found, and
    insert should replace an existing value or add a
    new key/value pair.
The cipher from lecture 4 can easily be modified to use a binary tree dictionary instead of a list.