560 likes | 683 Views
Introduction to ML. CS 331 Principles of Programming Languages revised Spring 2003. Features of ML. A pure functional language serious programs can be written without using variables Widely accepted reasonable performance (claimed) can be compiled
E N D
Introduction to ML CS 331 Principles of Programming Languages revised Spring 2003
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 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 athttp://www.smlnj.org
Running SML on linix.gl • The SML processor is available at/afs/umbc.edu/users/n/i/nicholas/pub/331/smlnj.linux/binor 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 world\n"); Hello world val it = () : unit -
Arithmetic in ML • Copy and paste the following text into a Standard ML window 2+2; (* note semicolon at end*) 3*4; 4/3; (* an error! *) 6 div 2; (* integer division *) 7 div 3;
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 freezingFahr = 32;
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(freezingFahr); Math.sqrt real(2); Math.sqrt(real(2)); Math.sqrt(real 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); stdIn: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 string concatenation, e.g. “house”^”cat” • \n is used for newline, as in C and C++
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 • Example, if quotient, dividend and divisor are reals, we might haveval quotient = if divisor > 0 then dividend/divisor else 0
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 • [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 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 list1 = [1,2,3]; val list1 = [1,2,3] : int list - val list2 = [3,4,5]; val list2 = [3,4,5] : int list - list1@list2; val it = [1,2,3,3,4,5] : int list - hd list1; val it = 1 : int - tl list2; val it = [4,5] : int list
More List Operations - val list1 = [1,2,3]; val list1 = [1,2,3] : int list - val list2 = [3,4,5]; val list2 = [3,4,5] : int list - 4::list1; val it = [4,1,2,3] : int list - val list3 = list1::list2; an error! - val list3=list1@list2; val list3 = [1,2,3,3,4,5] : int list - length(list3); val length(list3) = 6
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",#"o"] : char list - implode [#"c",#"a",#"t"]; val it = "cat" : string -
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 • ML will figure out the types
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 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 divBy2 x:int = x div 2 : int; fun divideBy2 (y : real) = y / 2.0; divBy2 (5); divideBy2 (5.0);
Two similar divide functions - fun divBy2 x:int = x div 2 : int; val divBy2 = fn : int -> int - fun divideBy2 (y : real) = y / 2.0; val divideBy2 = fn : real -> real - divBy2 (5); val it = 2 : int - divideBy2 (5.0); val it = 2.5 : real -
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::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 - MinList *) (* 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 MinList([]) = [] | MinList(x::xs) = if null(xs) then [x] else [Int.min(x,hd(MinList(xs)))]; MinList([]); MinList([1,2]); MinList([315, 41, 59, 265, 35, 897]);
When we run MinList,… - use "MinList.sml"; [opening MinList.sml] val MinList = 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 examples follow
(* Sample ML program - Abstract Syntax Trees *) (* Declare the ast datatype *) datatype ast = empty | leaf of int | node of string*ast*ast; fun traverse(empty) = print "empty tree" | traverse(leaf(n)) = (print (Int.toString(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 countUp(i:int, j:int) = if i=j then print(" "^Int.toString(j)) else (countUp(i,j-1);print(" "^Int.toString(j))); (* count from i to j *) fun TcountUp(i:int, j:int) = if i=j then print(" "^Int.toString(j)^"\n") else (print(" "^Int.toString(i));TcountUp(i+1,j));
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 loopIt(i:int,n:int,F) = if i = n then F(i) else let val dummy = F(i) val dummy2 = loopIt(i+1,n,F) in dummy2 (* any expression could be used *) end;
The Print Function • print(“This string\n”); • print(“2+2 is “^Int.toString(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 TextIO structure, open TextIO; • To open a file openIn(“somefile”); • The value returned is of type instream • endOfStream(file:instream): bool • inputN(file:instream,n:int):string • input(file:stream):string (* whole file *)
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 => 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 E1 then E2 else E3is equivalent tocase E1 of true => E2 | false => E3 • Example: if x<y then #”a” else #“b”is the same ascase x<y of true => #”a” | false => #“b” (* note same types *)
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 doesn’t matter,‘a list matches a list of any type • Example:fun listLen(x: ‘a list) = if x = nil then 0 else 1+listLen(tl(x));
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,[a1,a2,…,an]) produces the list [F(a1),F(a2),…,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,[a1,a2,…,an]) produces F(a1,F(a2,F(…,F(an-1, an)…))) • The reduce function may be implemented as follows (from Ullman)exception EmptyList;fun reduce (F, nil) = raise EmptyList| reduce (F, [a]) = a | reduce (F, x::xs) = F(x, reduce(F,xs));
More on reduce • Harper gives a more general form of reducefun reduce (unit, opn, nil) = unit | reduce (unit, opn, h::t) = opn(h, reduce (unit, opn, t)) • Example: two ways to sum a list of numbersfun add_up nil = 0| add_up(h::t) = h + add_up torfun 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 change, again from Harper’s book,fun better_reduce (unit, opn, alist) = let fun red nil = unit | red (h::t) = opn(h, red t)) in red alist end • We have less overhead by passing only those parameters that change
More on reduce • “Staging” helps even more! Again from Harperfun staged_reduce (unit, opn) = let fun red nil = unit | red (h::t) = opn(h, red t)) in red end • 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 [a1,a2,…,an] and returns the sublist such that P is true for every element of the sublist • To implement filterfun 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 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 finiteSequence = real list; • and these can be parameterized
Datatypes • Enumerated types, e.g. datatype berryType = raspberry | blueberry | blackberry; • So then we can say, for example, val b:berryType = raspberry;
Recursive Datatypes • Example: binary trees, where the values may be of some type ‘label: datatype ‘label btree = Empty |Node of ‘label * ‘label btree * ‘label btree • val inBinary: int btree = Node(5,Node(1,Empty,Empty),Empty)