1 / 79

Slides to accompany Ullman’s Functional Programming with ML

Slides to accompany Ullman’s Functional Programming with ML. CS 631 Principles of Programming Languages. Features of ML. A pure functional language serious programs can be written without using variables Widely accepted reasonable performance (claimed) can be compiled

rperry
Download Presentation

Slides to accompany Ullman’s Functional Programming with ML

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Slides to accompany Ullman’s Functional Programming with ML CS 631 Principles of Programming Languages

  2. 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

  3. 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

  4. Running SML on UNIX • On the cs machines, make sure the directory /cs/sml/bin is in your path. Then invoke sml from a shell prompt with the command sml • Use control d to exit interpreter

  5. Chapter 2 Getting Started in ML

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

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

  8. It should look like this

  9. 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;

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

  11. - 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

  12. 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++

  13. 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

  14. 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

  15. 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

  16. 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

  17. 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

  18. 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

  19. 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; val list3 = [[1,2,3],3,4,5] - length(list3); val length(list3) = 4

  20. 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 -

  21. 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?

  22. Chapter 3 Defining Functions

  23. Declaring Functions • A function takes an input value and returns an output value • ML will figure out the types fun fahrToCelsius f = (f -freezingFahr) * 5 div 9; fun celsiusToFahr c = c * 9 div 5 + freezingFahr;

  24. 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

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

  26. 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 -

  27. 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))

  28. 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?

  29. (* 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]);

  30. 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

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

  32. (* 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))));

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

  34. 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

  35. 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;

  36. Chapter 4 Input and Output

  37. 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

  38. 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 *)

  39. The option type constructor • The function input1(file) returns a value of type char option, which means that input1 returns • SOME c, where c is a character, or • NONE • The option type constructor can be used with lots of types, not just char

  40. Char option example fun makeList1(infile, NONE) = nil | makeList1(infile, SOME c) = c::makeList1(infile, input1(infile)); or fun makeList1(infile, c) = if isSome(c) then valOf(c)::makeList1(infile, input1(infile)) else nil; • In these examples, input1 is used to build a list of the characters in a file

  41. Chapter 5 More About Functions

  42. 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

  43. 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.

  44. 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 *)

  45. 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

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

  47. 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;

  48. 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)

  49. 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 Related