Princeton University
Computer Science Dept.

Computer Science 441
Programming Languages
Fall 1998

Lecture 2

ML

How to use the run-time system.

Before launching sml, you must add its directory to your path. Add /usr/local/sml/bin to your path.
For most of you, this will mean adding the following to your .cshrc file:

   setenv PATH ${PATH}:/usr/local/sml/bin

If you use the CS Dept's version of .cshrc, you will see the obvious place to uncomment a similar line and make minor changes.

To launch ML type:

   sml

System responds with message saying in ML, and then "-" prompt.

Can load definitions from UNIX file by typing:

   use "myfile.sml";

where myfile.sml is the name of your file. It should be in the same directory you were in when you typed sml.

Terminate session by typing control-D.

Evaluate expression by typing in and following with ";", e.g.

   - 3 + 5;
   val it = 8 : int

In the previous line (and later exampless), "-" is the prompt to the user, so the rest of the code on that line is what the user types in. The computer's response is shown directly below.

"it" refers to last value computed. Can also bind value to an identifier:

   - val six = 6;
   val six = 6 : int;

Thus typing an expression, exp, is equivalent to typing: val it = exp;

Identifier often called a variable, but really a constant declaration ("val" for value).

Can also define functions.

   - fun succ x = x + 1;
   val succ = fn : int -> int
   - succ 12;
   val it = 13 : int
   - 17 * (succ 3);
   val it = 68 : int;

Can also write:

   - val succ = fn x => x + 1;
   val succ = fn : int -> int

"fun" declaration tells compiler to look for fcn arguments.

Note semi-colon at top-level terminates parsing and causes evaluation.

No loops in the language, all functions written via recursion and if.. then.. else:

   - fun fact n = if n = 0 then 1 else n * fact (n-1);

Data types in ML

Built-in data types

unit, bool, int, real, strings, characters

overloading

If expression involves an overloaded operator (e.g., + , *, -), and no other clues as to what type the argument or result should be, used to get type-checking error:

   - fun double x = x + x;
   Type checking error in: (syntactic context unknown)
   Unresolvable overloaded identifier: +
   Definition cannot be found for the type: ('a * 'a) -> 'a
In SML97, assumes the argument must be int:
   - fun double x = x+x;
   val double = fn : int -> int
type declarations

Must put in types if want to be other than int function if there are no other clues to type inference.

Can include type info if like.

   - fun succ (x:real) = x + 1.0;

or

   - fun succ x : real = x + 1.0;

(which tells system that the result of the function is a real) or even

   - fun succ (x:real) :real  = x + 1.0;
though in these cases don't need to because clue of using "1.0" tells compiler that you want real addition!
Type constructors

tuples, records, lists

tuples

(17,"abc", true) : int * string * bool

records

{name = "bob",salary = 50000.99, rank=1}: {name: string, salary:real, rank:int}

Selectors:
#lab : {lab : 'a,...} -> 'a

Ex. of function on tuples:

   - fun power (m,n) = if n = 0 then 1
                                else m * power (m,n-1);
   val power = fn : (int * int) -> int

On the other hand

   - fun cpower m n = if n = 0 then 1
                                else m * cpower m (n-1);
   val cpower = fn : int -> (int -> int)

Note these are different functions!

Latter said to be in "Curried" form (after Haskell Curry).

Can define

   - val twopower = cpower 2
   val twopower = fn : int -> int
   - twopower 3;
   val it = 8 : int
lists

[2,3,4,5,6] - all elts must be of same type.

Operations:

Many kinds of lists:

nil is part of any list type,

   - nil;
   val it = [] : 'a list

where 'a stands for a type variable. Similarly write:

   - map;
   val it = fn: ('a -> 'b) -> (('a list) -> ('b list))

Map is first example of a polymorphic function.

Lists are built up using ::, can also be decomposed the same way,

  • i.e., [1,2,3] = 1::[2,3] = 1::2::[3] = 1::2::3::nil

    Can define functions by cases.

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

    Note that "=" is automatically printed on continuation line. Don't include it in your program files!

    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 fcn. comp. *)
    

    Here is how we could define a reverse fcn 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!

    Restrictions on type inference (including overloading problems)

    As noted earlier, type inference does not always interact well with overloading: arith ops, ordering (e.g. "<") - though it's better in sml97 than it was!

    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 real or function types or contain real or function types. Also only type variables allowed in equality types are those with ''.

    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
    

    Scoping

    ML uses 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

    Talk later about "lazy" or "call-by-need".

    Declarations and Order of operations:

    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);
    

    CS441 | CS Department | Princeton University