CSCI 431: ML Part II

Pattern matching in function definitions:

Last time defined
- fun product [] : int = 1
    | product (fst::rest) = fst * (product rest);

Can also use integers in patterns:

- fun oneTo 0 = []
=   | oneTo n = n::(oneTo (n-1));

- fun fact n = product (oneTo n);
Note oneTo 5 = [5,4,3,2,1]

Could have written

val fact = product o oneTo (* o is fun. comp. *)

Here is how we could define a reverse fun if it were not provided:

- fun reverse [] = []
=   | reverse (h::t) = reverse(t)@[h];  (* pattern matching *)

Pattern matching

Pattern matching is quite important in this language.

Rarely use hd or tl - list operators giving head and tail of list.

Note that hd (a::x) = a, tl(a::x) = x, and ((hd x) :: (tl x)) = x if x is a list with at least one element.

Can use pattern matching in relatively complex ways to bind variables:

- val (x,y) = (5 div 2, 5 mod 2);
> val x = 2 : int
> val y = 1 : int

- val head::tail = [1,2,3];
> val head = 1 : int
> val tail = [2,3] : int list

- val {a = x, b = y} = {b = 3, a = "one"};
> val x = "one" : string
> val y = 3 : int

- val head::_ = [4,5,6];  (* note use of wildcard "_" *)
> val head = 4 : int

Type inference

Language is strongly typed via type inference - infers type involving type variables if possible.

Thus

	hd : ('a list) -> 'a
	tl : ('a list) -> ('a list)
Define
	fun last [x] = x
	  | last (fst::snd::rest) = last (snd::rest);
has type 'a list -> 'a, but don't have to declare it!

Polymorphism (including overloading problems)

As noted earlier, type inference does not interact well with overloading: arith ops, ordering (e.g. "<")

Also need to distinguish "equality" types:

- fun search item [] = false
=   | search item (fst::rest) = if item = fst then true
=                                      else search item rest;
> val search = fn : ''a -> ((''a list) -> bool)
Double quote before variable name indicates "equality" type. Cannot use "=" on types which are function types or contain function types.

Local declarations (including parallel and sequential declarations).

Functions and values declared at top level (interactively) stay visible until a new definition is given to the identifier.
- val x = 3 * 3;
> val x = 9 : int;
- 2 * x;
> val it = 18 : int
Can also give local declarations of function and variables.
- fun roots (a,b,c) = 
=		let val disc = sqrt (b * b - 4.0 * a * c) 
=		in
=			((~b + disc)/(2.0*a),(~b - disc)/(2.0*a))
=		end;

- roots (1.0,5.0,6.0); > (~2.0,~3.0) : real * real - disc;

Type checking error in: disc Unbound value identifier: disc

Static scoping (unlike original LISP)
- val x = 3;
> val x = 3 : int
- fun f y = x + y;
> val f = fn : int -> int
- val x = 6;
> val x = 6 : int
- f 0;
What is answer? 3!!

Why? Because definition of f used first "x", not second.

ML employs "eager" or call-by-value parameter passing

Order of operations:

  • Evaluate operand, substitute operand value in for formal parameter, and evaluate.

  • Inside record, evaluate fields left to right.

  • Inside let expression of form "let decl in exp end":
  • evaluate decl producing new environment, evaluate exp in new environment,
  • restore old environment, return value of exp.
  • Can have sequential or parallel declarations:
    - val x = 12
    = val y = x +2;
    > val x = 12 : int
    > val y = 14 : int
    - val x = 2
    = and y = x + 3;
    > val x = 2 : int
    > val y = 15 : int
    
    However, when defining functions, simultaneous declaration supports mutual recursion.
    - fun f n = if n = 0 then 1 else g n
    = and g m = m * f(m-1);
    

    Examples

    Print every other element in a list:

    
    fun
        take (L) =
           if L = nil then nil
           else hd(L) :: skip(tl(L))
        and
        skip (L) =
           if L = nil then nil
            else take(tl(L)) ;
    


    Standard function to calculate the lenght of a list

    
    fun  length (nil) = 0
      |  length (x :: xs) = 1 + length xs; 
    


    The list member function

    
    fun  member (X, nil) = false
       | member (X, x::xs) =
            if (X = x)
                 then true
             else member (X, xs) ;
    


    What do these functions do?

    
    fun     index(0, h::t)  = h
         |  index(n, h::t)  = index(n-1, t);
         
    
    fun     takeN(0, h::t)  = nil
         |  takeN(n, h::t)  = h :: takeN(n-1, t);
    
    
    fun     dropN(0, x)     = x
         |  dropN(n, h::t)  = dropN(n-1,t);