Functions Over Lists Programmg Languages and Compilers (CS 421) Elsa L Gunter 2112 SC, UIUC http://courses.engr.illois.edu/cs421 Based part on slides by Mattox Beckman, as updated by Vikram Adve and Gul Agha # let rec double_up list = with [ ] -> [ ] (* pattern before ->, expression after *) (x :: xs) -> (x :: x :: double_up xs);; val double_up : 'a list -> 'a list = <fun> # let fib5_2 = double_up fib5;; val fib5_2 : t list = [8; 8; 5; 5; 3; 3; 2; 2; 1; 1; 1; 1] 9/11/17 1 9/11/17 2 Functions Over Lists Structural Recursion # let silly = double_up ["hi"; "there"];; val silly : strg list = ["hi"; "hi"; "there"; "there"] # let rec poor_rev list = with [] -> [] (x::xs) -> poor_rev xs @ [x];; val poor_rev : 'a list -> 'a list = <fun> # poor_rev silly;; - : strg list = ["there"; "there"; "hi"; "hi"] n Functions on recursive datatypes (eg lists) tend to be recursive n Recursion over recursive datatypes generally by structural recursion n Recursive calls made to components of structure of the same recursive type n Base cases of recursive types stop the recursion of the function 9/11/17 3 9/11/17 4 Structural Recursion : List Example # let rec length list = with [ ] -> 0 (* Nil case *) x :: xs -> 1 + length xs;; (* Cons case *) val length : 'a list -> t = <fun> # length [5; 4; 3; 2];; - : t = 4 n Nil case [ ] is base case n Cons case recurses on component list xs Forward Recursion n In Structural Recursion, split put to components and (eventually) recurse on components n Forward Recursion form of Structural Recursion n In forward recursion, first call the function recursively on all recursive components, and then build fal result from partial results n Wait until whole structure has been traversed to start buildg answer 9/11/17 5 9/11/17 6
Forward Recursion: Examples # let rec double_up list = with [ ] -> [ ] (x :: xs) -> (x :: x :: double_up xs);; val double_up : 'a list -> 'a list = <fun> n How do you write length with forward recursion? # let rec poor_rev list = with [] -> [] (x::xs) -> let pr = poor_rev xs pr @ [x];; val poor_rev : 'a list -> 'a list = <fun> 9/11/17 7 9/11/17 8 n How do you write length with forward recursion? match l with [] -> (a :: bs) -> n How do you write length with forward recursion? match l with [] -> (a :: bs) -> 1 + length bs 9/11/17 9 9/11/17 10 n How do you write length with forward recursion? match l with [] -> 0 (a :: bs) -> 1 + length bs Your turn now Try Problem 2 on ML2 9/11/17 11 9/11/17 12
An Important Optimization An Important Optimization Normal call h g f n When a function call is made, the return address needs to be saved to the stack so we know to where to return when the call is fished n What if f calls g and g calls h, but callg h is the last thg g does (a tail call)? Tail call h f n When a function call is made, the return address needs to be saved to the stack so we know to where to return when the call is fished n What if f calls g and g calls h, but callg h is the last thg g does (a tail call)? n Then h can return directly to f stead of g 9/11/17 13 9/11/17 14 Tail Recursion n A recursive program is tail recursive if all recursive calls are tail calls n Tail recursive programs may be optimized to be implemented as loops, thus removg the function call overhead for the recursive calls n Tail recursion generally requires extra accumulator arguments to pass partial results n May require an auxiliary function Example of Tail Recursion n Forward recursive: # let rec prod l = match l with [] -> 1 (x :: rem) -> x * prod rem;; val prod : t list -> t = <fun> n Tail recursive: # let prod list = let rec prod_aux l acc = match l with [] -> acc (y :: rest) -> prod_aux rest (acc * y) (* Uses associativity of multiplication *) prod_aux list 1;; val prod : t list -> t = <fun> 9/11/17 15 9/11/17 16 9/11/17 17 9/11/17 18
with [] -> (a :: bs) -> with [] -> n (a :: bs) -> 9/11/17 19 9/11/17 20 with [] -> n (a :: bs) -> length_aux with [] -> n (a :: bs) -> length_aux bs 9/11/17 21 9/11/17 22 with [] -> n (a :: bs) -> length_aux bs (n + 1) with [] -> n (a :: bs) -> length_aux bs (n + 1) length_aux l 0 9/11/17 23 9/11/17 24
Mappg Recursion Your turn now Try Problem 4 on MP2 n One common form of structural recursion applies a function to each element the structure # let rec doublelist list = with [ ] -> [ ] x::xs -> 2 * x :: doublelist xs;; val doublelist : t list -> t list = <fun> # doublelist [2;3;4];; - : t list = [4; 6; 8] 9/11/17 25 9/11/17 26 Mappg Functions Over Lists # let rec map f list = with [] -> [] (h::t) -> (f h) :: (map f t);; val map : ('a -> 'b) -> 'a list -> 'b list = <fun> # map plus_two fib5;; - : t list = [10; 7; 5; 4; 3; 3] # map (fun x -> x - 1) fib6;; : t list = [12; 7; 4; 2; 1; 0; 0] Mappg Recursion n Can use the higher-order recursive map function stead of direct recursion # let doublelist list = List.map (fun x -> 2 * x) list;; val doublelist : t list -> t list = <fun> # doublelist [2;3;4];; - : t list = [4; 6; 8] n Same function, but no rec 9/11/17 27 9/11/17 28 Your turn now Write a function make_app : (( a -> b) * a) list -> b list that takes a list of function put pairs and gives the result of applyg each function to its argument. Use map, no explicit recursion. let make_app l = Foldg Recursion n Another common form folds an operation over the elements of the structure # let rec multlist list = with [ ] -> 1 x::xs -> x * multlist xs;; # multlist [2;4;6];; - : t = 48 n Computes (2 * (4 * (6 * 1))) 9/11/17 29 9/11/17 30
Foldg Functions over Lists # let rec sumlist list = with [ ] -> 0 x::xs -> x + sumlist xs;; val sumlist : t list -> t = <fun> # sumlist [2;3;4];; # let rec prodlist list = with [ ] -> 1 x::xs -> x * prodlist xs;; val prodlist : t list -> t = <fun> # prodlist [2;3;4];; - : t = 24 Foldg Functions over Lists # let rec sumlist list = with [ ] -> 0 x::xs -> x + sumlist xs;; val sumlist : t list -> t = <fun> # sumlist [2;3;4];; Base Case # let rec multlist list = with [ ] -> 1 x::xs -> x * multlist xs;; # multlist [2;3;4];; - : t = 24 9/11/17 31 9/11/17 32 Foldg Functions over Lists # let rec sumlist list = with [ ] -> 0 x::xs -> x + sumlist xs;; val sumlist : t list -> t = <fun> # sumlist [2;3;4];; Recursive Call # let rec multlist list = with [ ] -> 1 x::xs -> x * multlist xs;; # multlist [2;3;4];; - : t = 24 Foldg Functions over Lists # let rec sumlist list = with [ ] -> 0 x::xs -> x + sumlist xs;; val sumlist : t list -> t = <fun> # sumlist [2;3;4];; Head Element # let rec multlist list = with [ ] -> 1 x::xs -> x * multlist xs;; # multlist [2;3;4];; - : t = 24 9/11/17 33 9/11/17 34 Foldg Functions over Lists # let rec sumlist list = with [ ] -> 0 x::xs -> x + sumlist xs;; val sumlist : t list -> t = <fun> # sumlist [2;3;4];; Combg Operation # let rec multlist list = with [ ] -> 1 x::xs -> x * multlist xs;; # multlist [2;3;4];; - : t = 24 Foldg Functions over Lists # let rec sumlist list = with [ ] -> 0 x::xs -> x + sumlist Rec value xs;; val sumlist : t list -> t = <fun> # sumlist [2;3;4];; Combg Operation # let rec multlist list = with [ ] -> 1 x::xs -> x * multlist Rec value xs;; # multlist [2;3;4];; - : t = 24R 9/11/17 35 9/11/17 36
Recursg over lists # let rec fold_right f list b = with [] -> b (x :: xs) -> f x (fold_right f xs b);; The Primitive Recursion Fairy val fold_right : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b = <fun> # fold_right (fun s -> fun () -> prt_strg s) ["hi"; "there"] ();; therehi- : unit = () Foldg Recursion n multlist folds to the right n Same as: # let multlist list = List.fold_right (fun x -> fun p -> x * p) list 1;; # multlist [2;4;6];; - : t = 48 9/11/17 37 9/11/17 38 Encodg Recursion with Fold # let rec append list1 list2 = 1 with [ ] -> list2 x::xs -> x :: append xs list2;; val append : 'a list -> 'a list -> 'a list = <fun> Base Case Operation Recursive Call # let append list1 list2 = fold_right (fun x y -> x :: y) list1 list2;; val append : 'a list -> 'a list -> 'a list = <fun> # append [1;2;3] [4;5;6];; - : t list = [1; 2; 3; 4; 5; 6] match l with [] -> 0 (a :: bs) -> 1 + length bs n How do you write length with fold_right, but no explicit recursion? 9/11/17 39 9/11/17 40 match l with [] -> 0 (a :: bs) -> 1 + length bs n How do you write length with fold_right, but no explicit recursion? let length list = List.fold_right (fun x -> fun n -> n + 1) list 0 Map from Fold # let map f list = fold_right (fun x -> fun y -> f x :: y) list [ ];; val map : ('a -> 'b) -> 'a list -> 'b list = <fun> # map ((+)1) [1;2;3];; - : t list = [2; 3; 4] n Can you write fold_right (or fold_left) with just map? How, or why not? 9/11/17 41 9/11/17 42
Iteratg over lists # let rec fold_left f a list = with [] -> a (x :: xs) -> fold_left f (f a x) xs;; val fold_left : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a = <fun> # fold_left (fun () -> prt_strg) () ["hi"; "there"];; hithere- : unit = () 9/11/17 43 Encodg Tail Recursion with fold_left # let prod list = let rec prod_aux l acc = match l with [] -> acc (y :: rest) -> prod_aux rest (acc * y) prod_aux list 1;; val prod : t list -> t = <fun> Init Acc Value Recursive Call Operation # let prod list = List.fold_left (fun acc y -> acc * y) 1 list;; val prod: t list -> t = <fun> # prod [4;5;6];; - : t =120 9/11/17 44 with [] -> n (a :: bs) -> length_aux bs (n + 1) length_aux l 0 n How do you write length with fold_left, but no explicit recursion? 9/11/17 45 with [] -> n (a :: bs) -> length_aux bs (n + 1) length_aux l 0 n How do you write length with fold_left, but no explicit recursion? let length list = List.fold_left (fun n -> fun x -> n + 1) 0 list 9/11/17 46 Foldg Recall # let rec fold_left f a list = with [] -> a (x :: xs) -> fold_left f (f a x) xs;; val fold_left : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a = <fun> fold_left f a [x 1 ; x 2 ; ;x n ] = f( (f (f a x 1 ) x 2 ) )x n # let rec fold_right f list b = with [ ] -> b (x :: xs) -> f x (fold_right f xs b);; val fold_right : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b = <fun> fold_right f [x 1 ; x 2 ; ;x n ] b = f x 1 (f x 2 ( (f x n b) )) # let rec poor_rev list = with [] -> [] (x::xs) -> poor_rev xs @ [x];; val poor_rev : 'a list -> 'a list = <fun> n What is its runng time? 9/11/17 47 9/11/17 48
Quadratic Time n Each step of the recursion takes time proportional to put n Each step of the recursion makes only one recursive call. n List example: # let rec poor_rev list = with [] -> [] (x::xs) -> poor_rev xs @ [x];; val poor_rev : 'a list -> 'a list = <fun> Tail Recursion - Example # let rec rev_aux list revlist = with [ ] -> revlist x :: xs -> rev_aux xs (x::revlist);; val rev_aux : 'a list -> 'a list -> 'a list = <fun> # let rev list = rev_aux list [ ];; val rev : 'a list -> 'a list = <fun> n What is its runng time? 9/11/17 49 9/11/17 50 Comparison n poor_rev [1,2,3] = n (poor_rev [2,3]) @ [1] = n ((poor_rev [3]) @ [2]) @ [1] = n (((poor_rev [ ]) @ [3]) @ [2]) @ [1] = n (([ ] @ [3]) @ [2]) @ [1]) = n ([3] @ [2]) @ [1] = n (3:: ([ ] @ [2])) @ [1] = n [3,2] @ [1] = n 3 :: ([2] @ [1]) = n 3 :: (2:: ([ ] @ [1])) = [3, 2, 1] Comparison n rev [1,2,3] = n rev_aux [1,2,3] [ ] = n rev_aux [2,3] [1] = n rev_aux [3] [2,1] = n rev_aux [ ] [3,2,1] = [3,2,1] 9/11/17 51 9/11/17 52 Foldg - Tail Recursion - # let rev list = - fold_left - (fun l -> fun x -> x :: l) //comb op [] //accumulator cell list Foldg n Can replace recursion by fold_right any forward primitive recursive defition n Primitive recursive means it only recurses on immediate subcomponents of recursive data structure n Can replace recursion by fold_left any tail primitive recursive defition 9/11/17 53 9/11/17 54
Contuation Passg Style n A programmg technique for all forms of non-local control flow: n non-local jumps n exceptions n general conversion of non-tail calls to tail calls n Essentially it s a higher-order function version of GOTO Contuations n Idea: Use functions to represent the control flow of a program n Method: Each procedure takes a function as an argument to which to pass its result; outer procedure returns no result n Function receivg the result called a contuation n Contuation acts as accumulator for work still to be done 9/11/17 55 9/11/17 56 Example of Tail Recursion # let rec app fl x = match fl with [] -> x (f :: rem_fs) -> f (app rem_fs x);; val app : ('a -> 'a) list -> 'a -> 'a = <fun> # let app fs x = let rec app_aux fl acc= match fl with [] -> acc (f :: rem_fs) -> app_aux rem_fs (fun z -> acc (f z)) app_aux fs (fun y -> y) x;; val app : ('a -> 'a) list -> 'a -> 'a = <fun> Contuation Passg Style n Writg procedures so that they take a contuation to which to give (pass) the result, and return no result, is called contuation passg style (CPS) 9/11/17 57 9/11/17 58 Example of Tail Recursion & CSP Contuation Passg Style # let app fs x = let rec app_aux fl acc= match fl with [] -> acc (f :: rem_fs) -> app_aux rem_fs (fun z -> acc (f z)) app_aux fs (fun y -> y) x;; val app : ('a -> 'a) list -> 'a -> 'a = <fun> # let rec appk fl x k = match fl with [] -> k x (f :: rem_fs) -> appk rem_fs x (fun z -> k (f z));; val appk : ('a -> 'a) list -> 'a -> ('a -> 'b) -> 'b n A compilation technique to implement nonlocal control flow, especially useful terpreters. n A formalization of non-local control flow denotational semantics n Possible termediate state compilg functional code 9/11/17 59 9/11/17 60
Terms n A function is Direct Style when it returns its result back to the caller. n A Tail Call occurs when a function returns the result of another function call without any more computations (eg tail recursion) n A function is Contuation Passg Style when it passes its result to another function. n Instead of returng the result to the caller, we pass it forward to another function. Contuation Passg Style n A compilation technique to implement nonlocal control flow, especially useful terpreters. n A formalization of non-local control flow denotational semantics n Possible termediate state compilg functional code 9/11/17 61 9/11/17 62 Example Simple Functions Takg Contuations n Simple reportg contuation: # let report x = (prt_t x; prt_newle( ) );; val report : t -> unit = <fun> n Simple function usg a contuation: # let plusk a b k = k (a + b) val plusk : t -> t -> (t -> a) -> a = <fun> # plusk 20 22 report;; 42 - : unit = () n Given a primitive operation, can convert it to pass its result forward to a contuation n Examples: # let subk x y k = k(x + y);; val subk : t -> t -> (t -> 'a) -> 'a = <fun> # let eqk x y k = k(x = y);; val eqk : 'a -> 'a -> (bool -> 'b) -> 'b = <fun> # let timesk x y k = k(x * y);; val timesk : t -> t -> (t -> 'a) -> 'a = <fun> 9/11/17 63 9/11/17 64 Nestg Contuations # let add_three x y z = x + y + z;; val add_three : t -> t -> t -> t = <fun> # let add_three x y z= let p = x + y p + z;; val add_three : t -> t -> t -> t = <fun> # let add_three_k x y z k = addk x y (fun p -> addk p z k );; val add_three_k : t -> t -> t -> (t -> 'a) -> 'a = <fun> 9/11/17 65