Introduction to ML CS 331 Principles of Programming

  • Slides: 56
Download presentation
Introduction to ML CS 331 Principles of Programming Languages revised Spring 2003

Introduction to ML CS 331 Principles of Programming Languages revised Spring 2003

Features of ML • A pure functional language – serious programs can be written

Features of ML • A pure functional language – serious programs can be written without using variables • Widely accepted – reasonable performance (claimed) – can be compiled – syntax not as arcane (or as simple) as LISP

In these slides, • We use Standard ML of New Jersey • Runs on

In these slides, • We use Standard ML of New Jersey • Runs on PCs, Linux, and other flavors of UNIX • Much of this material is based on Ullman’s book, Elements of ML Programming • See the SML documentation at http: //www. smlnj. org

Running SML on linix. gl • The SML processor is available at /afs/umbc. edu/users/n/i/nicholas/pub/331/smlnj.

Running SML on linix. gl • The SML processor is available at /afs/umbc. edu/users/n/i/nicholas/pub/331/smlnj. linux/bin or equivalently ~nicholas/. . /pub/331/smlnj. linux/bin • Add this directory to your path, and do a rehash • Then invoke sml from a shell prompt with the command sml • Use control d to exit interpreter

Hello, world in SML Standard ML of New Jersey, - print("Hello worldn"); Hello world

Hello, world in SML Standard ML of New Jersey, - print("Hello worldn"); Hello world val it = () : unit -

Arithmetic in ML • Copy and paste the following text into a Standard ML

Arithmetic in ML • Copy and paste the following text into a Standard ML window 2+2; 3*4; 4/3; 6 div 2; 7 div 3; (* note semicolon at end*) (* an error! *) (* integer division *)

Declaring Constants • Constants are not exactly the same as variables – they can

Declaring Constants • Constants are not exactly the same as variables – they can be redefined, but existing uses of that constant (e. g. in function definitions) aren’t affected by such redefinition val freezing. Fahr = 32;

Ints and Reals • Note ~ is unary minus • min and max take

Ints and Reals • Note ~ is unary minus • min and max take just two input arguments, but that can be fixed! • The real operator converts to real • Parens can sometimes be omitted, but I don’t recommend it Int. abs ~3; Int. sign ~3; Int. max (4, 7); Int. min (~2, 2); real(freezing. Fahr); Math. sqrt real(2); Math. sqrt(real(2)); Math. sqrt(real 3);

- Int. abs ~3; val it = 3 : int - Int. sign ~3;

- Int. abs ~3; val it = 3 : int - Int. sign ~3; val it = ~1 : int - Int. max (4, 7); val it = 7 : int - Int. min (~2, 2); val it = ~2 : int - Math. sqrt real(2); std. In: 57. 1 -57. 18 Error: operator and operand don't agree [tycon mismatch] operator domain: real operand: int -> real in expression: Math. sqrt real - Math. sqrt(real(2)); val it = 1. 41421356237 : real - Math. sqrt(real 3); val it = 1. 73205080757 : real

Strings • Delimited by double quotes • the caret mark ^ is used for

Strings • Delimited by double quotes • the caret mark ^ is used for string concatenation, e. g. “house”^”cat” • n is used for newline, as in C and C++

Comparison Operators • The usual <, >, <=, >= and <> are available •

Comparison Operators • The usual <, >, <=, >= and <> are available • For reals, = and <> are not available – For reals a and b, a <= b andalso a>= b is an equality test • The connectors “andalso” and “orelse” are logical operators with short-circuit evaluation

If Then Else • If Then Else is an expression, not a control structure

If Then Else • If Then Else is an expression, not a control structure • Example, if quotient, dividend and divisor are reals, we might have val quotient = if divisor > 0 then dividend/divisor else 0

Tuples • Tuples are data items drawn from a Cartesian product type. Example: type

Tuples • Tuples are data items drawn from a Cartesian product type. Example: type fraction = int * int; val foo: fraction = (44, 100); #1(foo); (* returns 44 *) #2(foo); (* returns 100 *) • Tuples are of fixed size, e. g. 2 in this example

Lists in ML • Objects in a list must be of the same type

Lists in ML • Objects in a list must be of the same type – [1, 2, 3]; – [“dog”, “cat”, “moose”]; • The empty list is written [] or nil

Making Lists • The @ operator is used to concatenate two lists of the

Making Lists • The @ operator is used to concatenate two lists of the same type • The : : operator makes a new list in which its first operand is the new first element of a list which is otherwise like the second operand. • The functions hd and tl give the first element of the list, and the rest of the list, respectively

List Operations - val list 1 = [1, 2, 3]; val list 1 =

List Operations - val list 1 = [1, 2, 3]; val list 1 = [1, 2, 3] : int list - val list 2 = [3, 4, 5]; val list 2 = [3, 4, 5] : int list - list 1@list 2; val it = [1, 2, 3, 3, 4, 5] : int list - hd list 1; val it = 1 : int - tl list 2; val it = [4, 5] : int list

More List Operations - val list 1 = [1, 2, 3]; val list 1

More List Operations - val list 1 = [1, 2, 3]; val list 1 = [1, 2, 3] : int list - val list 2 = [3, 4, 5]; val list 2 = [3, 4, 5] : int list - 4: : list 1; val it = [4, 1, 2, 3] : int list - val list 3 = list 1: : list 2; an error! - val list 3=list 1@list 2; val list 3 = [1, 2, 3, 3, 4, 5] : int list - length(list 3); val length(list 3) = 6

Strings and Lists • The explode function converts a string into a list of

Strings and Lists • The explode function converts a string into a list of characters • The implode function converts a list of characters into a string • Examples: - explode("foo"); val it = [#"f", #"o"] : char list - implode [#"c", #"a", #"t"]; val it = "cat" : string -

Heads and Tails • The cons operator : : takes an element and prepends

Heads and Tails • The cons operator : : takes an element and prepends it to a list of that same type. • For example, the expression 1: : [2, 3] results in the list [1, 2, 3] • What’s the value of [1, 2]: : [ [3, 4], [5, 6]] ? • What’s the value of x: : [], for any atom x?

Declaring Functions • A function takes an input value and returns an output value

Declaring Functions • A function takes an input value and returns an output value • ML will figure out the types

Notes • ML is picky about not mixing types, such as int and real,

Notes • ML is picky about not mixing types, such as int and real, in expressions • The value of “it” is always the last value computed • Function arguments don’t always need parentheses, but it doesn’t hurt to use them

Types of arguments and results • ML figures out the input and/or output types

Types of arguments and results • ML figures out the input and/or output types for simple expressions, constant declarations, and function declarations • If the default isn’t what you want, you can specify the input and output types, e. g. fun div. By 2 x: int = x div 2 : int; fun divide. By 2 (y : real) = y / 2. 0; div. By 2 (5); divide. By 2 (5. 0);

Two similar divide functions - fun div. By 2 x: int = x div

Two similar divide functions - fun div. By 2 x: int = x div 2 : int; val div. By 2 = fn : int -> int - fun divide. By 2 (y : real) = y / 2. 0; val divide. By 2 = fn : real -> real - div. By 2 (5); val it = 2 : int - divide. By 2 (5. 0); val it = 2. 5 : real -

Functions and Patterns • Recall that min and max take just two arguments •

Functions and Patterns • Recall that min and max take just two arguments • However, using the fact that, for example, – min(a, b, c) = min(a, min(b, c))

Generalizing Min • An example of ML pattern matching – the cons notation x:

Generalizing Min • An example of ML pattern matching – the cons notation x: : xs is both a binary constructor and a pattern – cases aren’t supposed to overlap • Note that lists of any size are supported – but the elements are expected to be integers – checking that the rest of the list is non-empty is critical - but why?

(* Sample ML program - Min. List *) (* Takes a list of integers

(* Sample ML program - Min. List *) (* Takes a list of integers as input, and returns a list with at most one element, i. e. the smallest element in the list *) fun Min. List([]) = [] | Min. List(x: : xs) = if null(xs) then [x] else [Int. min(x, hd(Min. List(xs)))]; Min. List([]); Min. List([1, 2]); Min. List([315, 41, 59, 265, 35, 897]);

When we run Min. List, … - use "Min. List. sml"; [opening Min. List.

When we run Min. List, … - use "Min. List. sml"; [opening Min. List. sml] val Min. List = fn : int list -> int list val it = [] : int list val it = [1] : int list val it = [35] : int list val it = () : unit

Building trees • It’s easy to build recursive data types in ML • Some

Building trees • It’s easy to build recursive data types in ML • Some examples follow

(* Sample ML program - Abstract Syntax Trees *) (* Declare the ast datatype

(* Sample ML program - Abstract Syntax Trees *) (* Declare the ast datatype *) datatype ast = empty | leaf of int | node of string*ast; fun traverse(empty) = print "empty tree" | traverse(leaf(n)) = (print (Int. to. String(n)); print " ") | traverse(node(operator, left, right)) = ( traverse(left); print operator; traverse(right)); fun prefix(tree: ast) = (traverse(tree); print "n"); prefix(empty); prefix(leaf(4)); prefix(node("*", node("+", leaf(5), leaf(3)), node("-", leaf(10), leaf(4))));

Two ways to count (* count from i to j *) fun count. Up(i:

Two ways to count (* count from i to j *) fun count. Up(i: int, j: int) = if i=j then print(" "^Int. to. String(j)) else (count. Up(i, j-1); print(" "^Int. to. String(j))); (* count from i to j *) fun Tcount. Up(i: int, j: int) = if i=j then print(" "^Int. to. String(j)^"n") else (print(" "^Int. to. String(i)); Tcount. Up(i+1, j));

What about control structures? • Well, there aren’t any in the usual (procedural) sense

What about control structures? • Well, there aren’t any in the usual (procedural) sense • If then else, case, and iteration are all accomplished by evaluation of expressions

Iteration vs. Recursion (* note that F is a functional parameter *) fun loop.

Iteration vs. Recursion (* note that F is a functional parameter *) fun loop. It(i: int, n: int, F) = if i = n then F(i) else let val dummy = F(i) val dummy 2 = loop. It(i+1, n, F) in dummy 2 (* any expression could be used *) end;

The Print Function • print(“This stringn”); • print(“ 2+2 is “^Int. to. String(2+2)^”n”); •

The Print Function • print(“This stringn”); • print(“ 2+2 is “^Int. to. String(2+2)^”n”); • Expressions may be grouped with parentheses, e. g (print(“a”); print(“b”)) • But the grouped expressions may not change the environment, so this is not the same as a block in a procedural language

More About I/O • To access functions in the Text. IO structure, open Text.

More About I/O • To access functions in the Text. IO structure, open Text. IO; • To open a file open. In(“somefile”); • The value returned is of type instream • end. Of. Stream(file: instream): bool • input. N(file: instream, n: int): string • input(file: stream): string (* whole file *)

Matches and Functions • Example of match expression: val rec reverse = fn nil

Matches and Functions • Example of match expression: val rec reverse = fn nil => nil| x: : xs => reverse(xs) @ [x]; • The rec keyword stands for “recursive”, which is necessary because the binding of reverse as a function name is not yet established

Anonymous Functions • Functions don’t have to have names, e. g. (fn x =>

Anonymous Functions • Functions don’t have to have names, e. g. (fn x => x+1) (3) yields 4 • Such functions can be passed as parameters, e. g. for use in the map or reduce functions, to be discussed later in this chapter.

If Then Else = Case • The familiar if E 1 then E 2

If Then Else = Case • The familiar if E 1 then E 2 else E 3 is equivalent to case E 1 of true => E 2 | false => E 3 • Example: if x<y then #”a” else #“b” is the same as case x<y of true => #”a” | false => #“b” (* note same types *)

Exceptions • • exception Foo and Bar; raise Foo; exception Foo of string; The

Exceptions • • exception Foo and Bar; raise Foo; exception Foo of string; The handle clause matches exceptions with (hopefully) suitable actions • Exceptions can be defined in let clauses

Polymorphic Functions • If you don’t know the type in advance, or if it

Polymorphic Functions • If you don’t know the type in advance, or if it doesn’t matter, ‘a list matches a list of any type • Example: fun list. Len(x: ‘a list) = if x = nil then 0 else 1+list. Len(tl(x));

Higher Order Functions • Functions may be passed as parameters, e. g. fun trap(a,

Higher Order Functions • Functions may be passed as parameters, e. g. fun trap(a, b, n, F)= if n <= 0 orelse b-a <= 0. 0 then 0. 0 else let val delta = (b-a)/real(n) in delta*(F(a)+F(a+delta))/2. 0+ trap(a+delta, b, n-1, F) end;

Higher-Order Function map • The map function map(F, [a 1, a 2, …, an])

Higher-Order Function map • The map function map(F, [a 1, a 2, …, an]) produces the list [F(a 1), F(a 2), …, F(an)] • The function may be defined (per Harper’s new ML book) fun map f nil = nil | map f (h: : t) = (f h): : (map f t)

Higher-Order Function reduce • The reduce function reduce(F, [a 1, a 2, …, an])

Higher-Order Function reduce • The reduce function reduce(F, [a 1, a 2, …, an]) produces F(a 1, F(a 2, F(…, F(an-1, an)…))) • The reduce function may be implemented as follows (from Ullman) exception Empty. List; fun reduce (F, nil) = raise Empty. List | reduce (F, [a]) = a | reduce (F, x: : xs) = F(x, reduce(F, xs));

More on reduce • Harper gives a more general form of reduce fun reduce

More on reduce • Harper gives a more general form of reduce fun reduce (unit, opn, nil) = unit | reduce (unit, opn, h: : t) = opn(h, reduce (unit, opn, t)) • Example: two ways to sum a list of numbers fun add_up nil = 0 | add_up(h: : t) = h + add_up t or fun add_up alist = reduce (0, op +, alist) • The op keyword allows + to be a parameter

More on reduce • To avoid passing unit and opn as parameters that don’t

More on reduce • To avoid passing unit and opn as parameters that don’t change, again from Harper’s book, fun better_reduce (unit, opn, alist) = = unit | red (h: : t) = opn(h, red t)) in red alist end let fun red nil • We have less overhead by passing only those parameters that change

More on reduce • “Staging” helps even more! Again from Harper fun staged_reduce (unit,

More on reduce • “Staging” helps even more! Again from Harper fun staged_reduce (unit, opn) = = unit | red (h: : t) = opn(h, red t)) in red end let fun red nil • We can use staged_reduce on many lists, e. g. reduce(unit, opn, alist) is the same as (but slower than) staged_reduce(unit, opn) alist

Higher-Order Function filter • The filter function takes a predicate P and a list

Higher-Order Function filter • The filter function takes a predicate P and a list [a 1, a 2, …, an] and returns the sublist such that P is true for every element of the sublist • To implement filter fun filter(P, nil) = nil | filter(P, x: : xs) = if P x then x: : filter(P, xs) else filter(P, xs)

The ML Type System • Basic types include int, real, string, char, bool, and

The ML Type System • Basic types include int, real, string, char, bool, and others • Tuple types, e. g. int*real*char • Function types, e. g. int->bool • Type constructors list and option – int list – char option

Creating Names for Types • type orderpair = int*int • type finite. Sequence =

Creating Names for Types • type orderpair = int*int • type finite. Sequence = real list; • and these can be parameterized

Datatypes • Enumerated types, e. g. datatype berry. Type = raspberry | blueberry |

Datatypes • Enumerated types, e. g. datatype berry. Type = raspberry | blueberry | blackberry; • So then we can say, for example, val b: berry. Type = raspberry;

Recursive Datatypes • Example: binary trees, where the values may be of some type

Recursive Datatypes • Example: binary trees, where the values may be of some type ‘label: datatype ‘label btree = Empty | Node of ‘label * ‘label btree • val in. Binary: int btree = Node(5, Node(1, Empty), Empty)

ASTs Revisited (* Sample ML program - Abstract Syntax Trees *) (* Assume that

ASTs Revisited (* Sample ML program - Abstract Syntax Trees *) (* Assume that terminal. Type and nonterminal. Type already known *) (* Declare the ast datatype *) datatype ast = empty | leaf of terminal. Type | node of nonterminal. Type*(ast list); fun traverse(empty) = print "empty tree" | traverse(leaf(t)) = (print. Terminal t; print " ") | traverse(node(nt, []) = print. Nonterminal(nt) | traverse(node(nt, x: : xs)) = (print. Nonterminal(nt); traverse(x); traverse. List(xs)) and fun traverse. List([]) = print “ “ | traverse. List(x: : xs) = (traverse(x); traverse. List(xs));

Record Structures • Records are wrapped in curly braces, and fields are separated by

Record Structures • Records are wrapped in curly braces, and fields are separated by commas • Field names may be used to refer to specific elements of the record

Record Example - type address. Type = {street: string, city: string, zip: int}; type

Record Example - type address. Type = {street: string, city: string, zip: int}; type address. Type = {city: string, street: string, zip: int} (note that SML sorted the fields alphabetically) - val umbc: address. Type = {street="1000 Hilltop Circle", city="Baltimore", zip=21250}; val umbc = {city="Baltimore", street="1000 Hilltop Circle", zip=21250} : address. Type - #city(umbc); val it = "Baltimore" : string

Pattern Matching in Records • Pattern matching works, as in x as {street=xstr, city=xcity,

Pattern Matching in Records • Pattern matching works, as in x as {street=xstr, city=xcity, zip=xzip}: : xs • If we don’t care about all the fields, use an ellipsis, e. g. x as {street=xstr, …}: : xs • Or even x as {city, …}

Arrays • open Array; val zero. Vector = array(100, 0); • sub(zero. Vector, 0)

Arrays • open Array; val zero. Vector = array(100, 0); • sub(zero. Vector, 0) is zero, as is sub(zero. Vector, 99) • update(zero. Vector, 2, 3. 14) changes the third element of the (now misnamed) zero. Vector

Case Studies • Hash tables – Make an array of hash buckets, each bucket

Case Studies • Hash tables – Make an array of hash buckets, each bucket containing a simple list of values • Triangularization of a matrix – If the array has m rows and n columns, make an array of m elements, each element being an array of n elements.