700 likes | 860 Views
New Language : ML Main topics: ML syntax Static typing, explicit data types Parameter 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
E N D
New Language: ML Main topics: ML syntax Static typing, explicit data types Parameter 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 - valseconds = 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 -> int Same 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 - valsquare = fn x : real => x*x; • valsquare = fn : real -> real - val square = fn x => x*x : real; • valsquare = fn : real -> real - val square = fn x : int => x*x : real; • Error: expression doesn't match constraint [tyconmismatch] 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 * real operand: int * int in 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 * real operand: real * int in 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); valit = (1,2,3) : int * int * int - valzeropair = (0.0,0.0); • val zeropair = (0.0,0.0) : real * real - valzero_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); • valnegpair = 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.
valsize = fn(n) => if n>0 andalso n<100 then "small“ else 100 Error: types of if branches do not agree [literal] then branch: string else branch: int in 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. valfact-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 = fnn:int => if n=0 then 1 else n*fact(n-1); • Error: unbound variable or constructor: fact • Why?
rec keyword valrec fact = fnn:int => if n=0 then 1 else n * fact(n-1); • valfact = 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 = fn0 => 1 | 1 => 1 * fact(0); • Warning: match nonexhaustive 0 => ... 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); valsum = 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 let val m : int = 3 val n : int = m*m in m * n end; valit = 27 : int
val fact = let val rec iter = fn (0, result) => result | (count, result) => iter(count-1, count*result) in fn 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: vallist_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 variable type 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)); • valfoldr = 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 constructor Sunday… 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 = MailBoxof 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 • valeq_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 • datatypeexpr = Constof 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_tree Value constructors: Node (3 params), Empty (no params)
(* Signature: tree_size Purpose: Calculate the size (number of nodes) in a binary tree Type: 'a binary_tree -> int *) - val rec tree_size = fn Empty => 0 | Node(lft, _, rht) => (1 + tree_size(lft) + tree_size(rht)); Response: valtree_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;
Motivation (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.) (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
More Motivation Some data types are inherently infinite Integers, Reals,… Can we generate a list of “all” integers? YES! (if at each point we only materialize a finite prefix..)
Main Idea: Delayed evaluation 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 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 datatype 'a seq = Nil | Cons of 'a * (unit -> 'a seq); Cons(1,Nil)=> error
Sequences datatype 'a seq = Nil | Cons of 'a * (unit -> 'a seq); Cons(1,Nil)=> error
Sequences datatype 'a seq = Nil | Cons of 'a * (unit -> 'a seq); Cons(1,Nil)=> error Cons(1, (fn() => Nil)) CORRECT