New Language: MLMain topics:ML syntax
Static typing, explicit data typesParameter matching
Lazy lists
Static vs. Dynamic Typing
• Static Typing: Typing is (can be) done before run-time– Usually at compile-time– Advantage: safer– C,C++,Java
• Dynamic Typing: Typing is done at run-time– Usually when there is no compilation phase– Scheme
• ML is a functional language with static typing!
ML features
• Functional
• Allows polymorphic and recursive user defined datatypes
• Supports static type inference– Types can either be explicitly defined, or inferred
ML Basics
In Scheme, declaration of names in the global env: (define <name> <exp>)In ML: val <name> = <exp>;
Type information is optional: inferred if not given
- val seconds = 60;•val seconds = 60 : int- val minutes = 60;•val minutes = 60 : int- val hours = 24;•val hours = 24 : int- seconds * minutes * hours;•val it = 86400 : int- it;•val it = 86400 : int- it*3;•val it = 259200 : int- val secInHour_times3 = it;•val secInHour_times3 = 259200 : int
Functions
- fn x => x*x;•val it = fn : int -> intSame as:-fn(x) => x*x;Application:- (fn(x) => x*x) 3; OR (fn(x) => x*x) (3);•val it = 9 : int-(fn x => x+1) ((fn x => x+1) 4); val it = 6 : int
Types and Functions- val square = fn x : real => x*x;•val square = fn : real -> real- val square = fn x => x*x : real;•val square = fn : real -> real- val square = fn x : int => x*x : real;• Error: expression doesn't match constraint [tycon mismatch] expression: int constraint: real in expression: x * x: real
Multiple arguments- val average = fn( x,y) => (x+y) /2.0;val average = fn : real * real -> real- average(3,5);Error: operator and operand don't agree [literal]operator domain: real * realoperand: int * intin expression:average (3,5)- average(3.0,5.0);val it = 4.0 : real- val average1 = fn(x,y) => (x+y) /2;Error: operator and operand don't agree [literal]operator domain: real * realoperand: real * intin expression:(x + y) / 2
Tuple Datatype
• real*real: The type of all real pairs.• int*real: The type of all integer-real pairs.• (real*real)*(real*real): The type of all pair of
real pairs.• real*int*(int*int):A type of triplets of all real,
integer and integer-pairs.• eal*(real -> int): The type of all pairs of a real
number and a function from real to integers.
Cont.
- (1,2);•val it = (1,2) : int * int- (1,2,3);val it = (1,2,3) : int * int * int- val zeropair = (0.0,0.0);•val zeropair = (0.0,0.0) : real * real- val zero_NegOne = (0.0,~1.0);•val zero_NegOne = (0.0,~1.0) : real * real- (zeropair, zero_NegOne);•val it = ((0.0,0.0),(0.0,~1.0)) : (real * real) * (real * real)- val negpair = fn(x,y) => (~x,~y);•val negpair = fn : int * int -> int * int
String Datatype
- "Monday" ^ "Tuesday";•val it = "MondayTuesday" : string- size(it);•val it = 13 : int- val title = fn name => "Dr. " ^ name;•val title = fn : string -> string- title ("Racket”);•val it = "Dr. Racket" : string
Conditionals and Booleans
- val sign = fn (n) => if n>0 then 1 else if n=0 then 0 else ~1 (* n<0 *);•val sign = fn : int -> int- sign(~3);•val it = ~1 : int
Arithmetic relations: <, >, <=, >=.Logic operators: andalso, orelse, not.
val size = fn(n) => if n>0 andalso n<100 then "small“ else 100
Error: types of if branches do not agree [literal]then branch: stringelse branch: intin expression:if (n>0) andalso (n<100) then "small" else 100
Notes
1. Using - in symbol names is not allowed : it is interpreted as the - operator.val fact-iter = 3;•Error: non-constructor applied to argument in pattern: -==> use _.2. ML is case sensitive!3. Order of definitions matter (see next)4. Recursive functions require special treatment (see next)
Recursive Functions
• In Scheme:(define fact (lambda (n) (if (= n 0) 1 (* n (fact (- n 1))))))
• - val fact = fn n:int => if n=0 then 1 else n*fact(n-1);• Error: unbound variable or constructor: fact• Why?
rec keyword
val rec fact = fn n:int => if n=0 then 1 else n * fact(n-1);
•val fact = fn : int -> int
Mutual Recursion
val rec isEven = fn n:int => if n=0 then true else not isOdd(n-1);
and val rec isOdd = fn n:int => if n=0 then false else not isEven(n-1);
Patterns in Function Arguments- val rec fact = fn 0 => 1 | n => n * fact(n-1);val fact = fn : int -> int
- val rec fact = fn 0 => 1 | 1 => 1 * fact(0);•Warning: match nonexhaustive0 => ...1 => ...val fact = fn : int -> int
val rec ackermann =fn (0,b) => b+1 | (a,0) => ackermann(a-1,1) | (a,b) => ackermann(a-1, ackermann(a, b-1));
val ackermann = fn : int * int -> int
“Wildcard”
- val or =fn (true, _) => true| (_, true) => true| (_, _) => false;val or = fn : bool * bool -> bool
High-order procedures
- val rec sum = fn (term, a, next, b) => if a>b then 0 else term(a)+sum(term,next(a),next,b);
val sum = fn : (int -> int) * int * (int -> int) * int -> int
average_damp
- val average_damp = fn f => (fn x => (x+f(x))/2.0);
val average_damp = fn : (real -> real) -> real -> real
let
letval m : int = 3val n : int = m*minm * nend;
val it = 27 : int
val fact =letval rec iter =fn (0, result) => result | (count, result) => iter(count-1, count*result)infn n => iter(n, 1)end;val fact = fn : int -> int
Lists in ML• val rec list_length = fn(h::t) => 1+list_length(t) | nil => 0;
Response: val list_length = fn:'a list -> int
:: is the value constructor (like cons in scheme)nil =[] (= scheme NULL)All elements of the list must be of the same type (homogenous list)
Polymorphic type: ‘a is a type variabletype string_list = string list;
head and tail
val head =fn h::_ => h;
val tail=fn _::t => t;
What about NULLs?
exception Empty;val head =fn nil => raise Empty | h::_ => h;val head = fn : 'a list -> 'a
Example: foldr
val rec foldr =fn (f, e, []) => e | (f, e, (h :: tl) ) => f(h, foldr(f, e, tl));
•val foldr = fn : (('a * 'b -> 'b) * 'b * 'a list) -> 'b
Example: Key-value storage
val rec assoc = fn (str:string, []) => 0 | (str, ((key, value)::s)) => if (str=key) then value else assoc(str, s);
Adding new types
Simple: Atomic User-Defined Types
datatype week = Sunday | Monday | Tuesday | Wednesday | Thursday |Friday | Saturday
week is a type constructorSunday… are the value constructors (convention: start with upper case letter)
Eager list implementation
datatype pair_selector_name = Car | Cdr;datatype pair_selector_name = Car | Cdr;
val cons = fn(x,y) => fn Car => x | Cdr => y;
fn : ('a * 'a) -> (pair_selector_name -> 'a)
Composite User Defined Types
• datatype address = MailBox of int | CityCon1 of string * string * int | CityCon2 of string * string * string * int | Village of string * string;
CityCon1, CityCon2, Village are the value constructors
Equality predicate for addresses
• val eq_address = fn (CityCon1(city, street, number), CityCon2(city', _, street', number')) => city=city' andalso street=street' andalso number = number' | (x, y) => x=y;
Patterns Revisited
• A powerful tool for manipulating different data types– No need for explicit predicates, selectors
• Can’t be used for equality via the repeated use of the same variable name– In fact no symbol can occur twice in the pattern
except for “_”
Equality types
• We have used “=“ to compare both ints and strings– Real.== is used for reals
• “=“ is then automatically defined for complex types if defined for each of their components
• Only such types can be used in patterns• If “=“ is used for a type not known to be an
equality type, a warning will be issued
Recursive Types
• datatype expr = Const of real | Add of expr * expr | Sub of expr * expr | Mul of expr * expr | Div of expr * expr;
Trees: Recursive Polymorphic Types
datatype 'a binary_tree = Empty| Node of 'a binary_tree * 'a * 'a binary_tree;
Type constructor: binary_treeValue constructors: Node (3 params), Empty (no params)
(*Signature: tree_sizePurpose: Calculate the size (number of nodes) in a binary treeType: 'a binary_tree -> int *)- val rec tree_size =fn Empty => 0 | Node(lft, _, rht) => (1 + tree_size(lft) + tree_size(rht));
Response: val tree_size = fn : 'a binary_tree -> int
Explicit “unioning” through constructors
datatype int_or_string = Int of int | String of string;type int_or_string_binary_tree = int_or_string binary_tree;
Lazy Lists
Motivation
42
(define (sum-primes a b) (define (iter count accum) (cond ((> count b) accum) ((prime? count) (iter (+ count 1) (+ count accum))) (else (iter (+ count 1) accum)))) (iter a 0))
(define (sum-primes a b) (accumulate + 0 (filter prime? (enumerate-interval a b))))
Second implementation consumes a lot of storage
Motivation (Cont.)
43
(car (cdr (filter prime? (enumerate-interval 10000 1000000))))
Requires a lot of time and space
How can we gain the efficiency of iteration, and retain the elegance of sequence-operations?
In ML:Lazy Lists! (Sequences)
Remember scheme applicative vs. normal order evaluation?
• Normal (Lazy) Order Evaluation:– go ahead and apply operator with unevaluated
argument subexpressions– evaluate a subexpression only when value is needed
• to print• by primitive procedure (that is, primitive procedures are
"strict" in their arguments)
• Compromise approach: give programmer control between normal and applicative order.
• Lazy lists: lists with delays
44
More Motivation
45
Some data types are inherently infiniteIntegers, Reals,…
Can we generate a list of “all” integers?
YES! (if at each point we only materialize a finite prefix..)
Main Idea: Delayed evaluation
46
A list is a pair of (item, remaining list)We will generate a pair (item,promise_to_generate_remaining_list)
Concrete ideas for implementation of a “promise”?
Sequences in ML
47
A list is a pair of (item, remaining list)We will generate a pair (item,promise_to_generate_remaining_list)
Concrete ideas for implementation of a “promise”?
Sequences
48
datatype 'a seq = Nil | Cons of 'a * (unit -> 'a seq);
Cons(1,Nil)=> error
Sequences
49
datatype 'a seq = Nil | Cons of 'a * (unit -> 'a seq);
Cons(1,Nil)=> error
Sequences
50
datatype 'a seq = Nil | Cons of 'a * (unit -> 'a seq);
Cons(1,Nil)=> error
Cons(1, (fn() => Nil)) CORRECT
Sequences
51
datatype 'a seq = Nil | Cons of 'a * (unit -> 'a seq);
Cons(1,Nil)=> error
Cons(1, (fn() => Nil)) CORRECT
Sequence head and tail
exception Empty; -val head = fn Cons(h,tl) => h
| Nil => raise Empty;
-val tail= fn Cons(h,tl) => tl)( | Nil => raise Empty;
take (first n elements)
val rec take = fn (seq, 0) => [ ]| (Nil, n) => raise Subscript| (Cons(h, tl), n) => h :: take( tl(), n-1);
Infinite sequences
val rec integers_from =fn k => Cons(k, (fn() =>integers_from(k+1)) );
val rec ones=Fn() => Cons(1, (fn() => ones())) );
Definition: an item belongs to an infinite sequence if reachable by finite number of invocations of tail
Using sequences
- head(integers_from (1));val it = 1 : int- integers_from(1);val it = Cons (1,fn) : int seq- tail it;val it = Cons (2,fn) : int seq- tail it;val it = Cons (3,fn) : int seq- take(integers_from(30), 7);val it = [30,31,32,33,34,35,36] : int list
Processing sequences
val rec squares =fn Nil => Nil| Cons(h, tl) => Cons(h*h, (fn()=>squares( tl () )) );val squares = fn : int seq -> int seq
Processing sequences
val rec seq_add =fn (Cons(h1, tl1), Cons(h2, tl2)) =>Cons(h1+h2, (fn() => seq_add(tl1(), tl2() ) ) )| (_,_) => Nil;
val seq_add = fn : int seq * int seq -> int seq
?Appending sequences
val rec list_append =fn ([], lst) => lst| (h :: lst1, lst2) => h :: list_append(lst1, lst2);val list_append = fn : 'a list * 'a list -> 'a list
val rec seq_append =fn (Nil, seq) => seq| (Cons(h, seq1), seq2) => Cons(h, (fn() => seq_append( seq1(), seq2) ) );val seq_append = fn : 'a seq * 'a seq -> 'a seq
Problem
What if seq1 is infinite??
val rec seq_append =fn (Nil, seq) => seq| (Cons(h, seq1), seq2) => Cons(h, (fn() => seq_append( seq1(), seq2) ) );val seq_append = fn : 'a seq * 'a seq -> 'a seq
Interleave
val rec seq_append =fn (Nil, seq) => seq| (Cons(h, seq1), seq2) => Cons(h, (fn() => seq_append( seq1(), seq2) ) );val seq_append = fn : 'a seq * 'a seq -> 'a seq
val rec interleave =fn (Nil, seq) => seq| (Cons(h, tl), seq) => Cons(h, (fn() => interleave(seq, tl() ) ) );
Interleave
val rec seq_append =fn (Nil, seq) => seq| (Cons(h, seq1), seq2) => Cons(h, (fn() => seq_append( seq1(), seq2) ) );val seq_append = fn : 'a seq * 'a seq -> 'a seq
val rec interleave =fn (Nil, seq) => seq| (Cons(h, tl), seq) => Cons(h, (fn() => interleave(seq, tl() ) ) );
More sequence processing
val rec seq_filter= fn (pred, Nil) => Nil
( |pred, Cons(h,tl)>= )if pred(h)then Cons(h, (fn()=>seq_filter(pred, tl() )) )else seq_filter(pred, tl() );
val rec seq_map =fn (f, Nil) => Nil| (f, Cons(h,tl)) =>Cons( f(h), (fn() => seq_map(f, tl() )) );val seq_map = fn : ('a -> 'b) * 'a seq -> 'b seq
63
Infinite sequences of pairsWant to produce the sequence of pairs of all integers (i,j) with i j and bind it to int-pairs.
Abstraction:
Let’s solve an even more general problem. Given two streams
S=(S1, S2 . . . . .) T=(T1, T2 . . . . .)
Consider the infinite rectangular array
(S1,T1) (S1,T2) (S1,T3) . . .(S2,T1) (S2,T2) (S2,T3) . . .(S3,T1) (S3,T2) (S3,T3) . . .. . . . . . . . .
64
Infinite sequence of pairs(S1,T1) (S1,T2) (S1,T3) . . .(S2,T1) (S2,T2) (S2,T3) . . .(S3,T1) (S3,T2) (S3,T3) . . .. . . . . . . . .Wish to produce all pairs that lie on or above the diagonal:
(S1,T1) (S1,T2) (S1,T3) . . . (S2,T2) (S2,T3) . . . (S3,T3) . . . . . .
If S and T are both the integers then this would be int-pairs
j
i
65
Infinite sequence of pairs(S1,T1) (S1,T2) (S1,T3) . . . (S2,T2) (S2,T3) . . . (S3,T3) . . . . . .
Write a function pairs such that (pairs s t) would be the desired sequence.
Wishful thinking:
Break the desired sequence into pieces
How do we combine?
1
2
66
Infinite sequence of pairs
val rec pairs=fn(Nil,…)=>…fn (Cons(h,tl), Cons(h1,tl1)) => interleave ( seq_map( fn(x) => (h,x)), Cons(h1,tl1)), pairs(tl(),tl1()) )
INFINITE LOOP!!!!
67
Infinite sequence of pairs(S1,T1) (S1,T2) (S1,T3) . . . (S2,T2) (S2,T3) . . . (S3,T3) . . . . . .
1 2
3
val rec pairs=fn(Nil,…)=>…fn (Cons(h,tl), Cons(h1,tl1)) => Cons( (h,h1), fn()=> interleave ( seq_map( fn(x) => (h,x)), tl1()), pairs(tl(),tl1()) )
Now recursion is delayed
Filter- curried version
Stream of all primes?
Sieve
Top Related