1 / 74

CS51: Polymorphism & Higher-Order Functions

CS51: Polymorphism & Higher-Order Functions. Greg Morrisett. Parameterization. Remember this picture?. Some Design & Coding Rules. Laziness can be a really good force in design. Never write the same code twice. factor out the common bits into a re-usable procedure.

zinnia
Télécharger la présentation

CS51: Polymorphism & Higher-Order Functions

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. CS51: Polymorphism & Higher-Order Functions Greg Morrisett

  2. Parameterization Remember this picture?

  3. Some Design & Coding Rules • Laziness can be a really good force in design. • Never write the same code twice. • factor out the common bits into a re-usable procedure. • better, use someone else’s (well-tested, well-documented, and well-maintained) procedure. • Why is this a good idea? • why don’t we just cut-and-paste snippets of code using the editor instead of abstracting them into procedures?

  4. Some Design & Coding Rules • Laziness can be a really good force in design. • Never write the same code twice. • factor out the common bits into a re-usable procedure. • better, use someone else’s (well-tested, well-documented, and well-maintained) procedure. • Why is this a good idea? • why don’t we just cut-and-paste snippets of code using the editor instead of abstracting them into procedures? • find and fix a bug in one copy, have to fix in all of them. • decide to change the functionality, have to track down all of the places where it gets used.

  5. Factoring Code in Ocaml Consider these definitions: let recinc_all (xs:int list) : int list = match xswith | [] -> [] | hd::tl -> (hd+1)::(inc_all tl) let recsquare_all (xs:intlist):int list = match xswith | [] -> [] | hd::tl -> (hd*hd)::(square_alltl)

  6. Factoring Code in Ocaml The code is largely the same – so factor it out! let recinc_all (xs:int list) : int list = match xswith | [] -> [] | hd::tl -> (hd+1)::(inc_all tl) let recsquare_all (xs:intlist):int list = match xswith | [] -> [] | hd::tl -> (hd*hd)::(square_alltl)

  7. Factoring Code in Ocaml The code is largely the same – so factor it out! let recmap (f:int->int) (xs:int list) : int list = match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl);; let inc x = x+1;; let inc_allxs = map inc xs;; let square y = y*y;; let square_allxs = map square xs;;

  8. Factoring Code in Ocaml Writing little functions like inc just so we call map is a pain. map is a higher-order function: it takes a function as an argument. let recmap (f:int->int) (xs:int list) : int list = match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl);; let inc x = x+1;; let inc_allxs = map inc xs;; let square y = y*y;; let square_allxs = map square xs;;

  9. Factoring Code in Ocaml We can use an anonymous function instead. map is a higher-order function: it takes a function as an argument. let recmap (f:int->int) (xs:int list) : int list = match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl);; let inc x = x+1;; let inc_allxs = map (fun x -> x+1) xs;; let square y = y*y;; let square_allxs = map (fun y -> y*y) xs;;

  10. Factoring Code in Ocaml We can use an anonymous function instead. map is a higher-order function: it takes a function as an argument. let recmap (f:int->int) (xs:int list) : int list = match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl);; let inc_allxs = map (fun x -> x+1) xs;; let square_allxs = map (fun y -> y*y) xs;;

  11. Factoring Code in Ocaml Originally, Church wrote this function using linstead of fun: (lx. x+1) or (lx. x*x) map is a higher-order function: it takes a function as an argument. let recmap (f:int->int) (xs:int list) : int list = match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl);; let inc_allxs = map (fun x -> x+1) xs;; let square_allxs = map (fun y -> y*y) xs;;

  12. Another example: let recsum (xs:int list) : int = match xswith | [] -> 0 | hd::tl -> hd + (sum tl);; let recprod (xs:int list) : int = match xswith | [] -> 1 | hd::tl -> hd * (prod tl);;

  13. Another example: let add xy = x + y;; let mulxy = x * y;; let recsum (xs:int list) : int = match xswith | [] -> 0 | hd::tl -> add hd (sum tl);; let recprod (xs:int list) : int = match xswith | [] -> 1 | hd::tl -> mulhd (prod tl);;

  14. Another example: let add xy = x + y;; let mulxy = x * y;; let recsum (xs:int list) : int = match xswith | [] -> 0 | hd::tl -> add hd (sum tl);; let recprod (xs:int list) : int = match xswith | [] -> 1 | hd::tl -> mulhd (prod tl);; let recreduce (f:int->int->int) (u:int) (xs:int list) : int = match xswith | [] -> u | hd::tl -> fhd (reduce futl);;

  15. A generic reducer: let add xy = x + y;; let mulxy = x * y;; let recreduce (f:int->int->int) (u:int) (xs:int list) : int = match xswith | [] -> u | hd::tl -> fhd (reduce futl);; let sum xs = reduce add 0 xs ;; let prod xs = reduce mul 1 xs ;;

  16. Reducer with anonymous functions: let add xy = x + y;; let mulxy = x * y;; let recreduce (f:int->int->int) (u:int) (xs:int list) : int = match xswith | [] -> u | hd::tl -> fhd (reduce futl);; let sum xs = reduce (fun xy -> x + y) 0 xs ;; let prod xs = reduce (fun xy -> x * y) 1 xs ;; let sum_squaresxs = sum (map square xs) ;;

  17. More on Anonymous Functions Function declarations are actually abbreviations: • let square x = x*x ;; • let add xy = x+y ;; are syntactic sugar for: • let square = (fun x -> x*x) ;; • let add = (fun xy -> x+y) ;; So, fun’s are values we can bind to a variable, just like 3 or “moo” or true. (Orthogonal design).

  18. One argument, one result Actually, functions are even simpler. All functions take one argument and return one result. So, • let add = (fun xy -> x+y) is shorthand for: • let add = (fun x -> (fun y -> x+y)) That is, add is a function which when given a value x, returns a function (fun y -> x+y) which when given a value y, returns x+y.

  19. Curried Functions (fun x -> (fun y -> x+y)) Currying: encoding a multi-argumentfunction using nested, higher-order functions. Named after the logician Haskell B. Curry. • was trying to find minimal logics that are powerful enough to encode traditional logics. • much easier to prove something about a logic with 3 connectives than one with 20. • the ideas translate directly to math (set & category theory) as well as to computer science. • (actually, Curry ripped off Moses Schönfinkel)

  20. What is the type of add? let add = (fun x -> (fun y -> x+y));; Add’s type is written: int -> int -> int which is short-hand for: int -> (int -> int) That is, the arrow type is right-associative.

  21. What’s so good about Currying? In addition to simplifying the language (orthogonal design), currying functions so that they only take one argument leads to two major wins: We can partially apply a function. We can more easily compose functions.

  22. Partial Application let add = (fun x -> (fun y -> x+y));; let inc = add 1;; Equivalent to writing: let inc = (fun y -> 1+y);; which is equivalent to writing: let inc y = 1+y;;

  23. Simplifying Definitions let recmap fxs = match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl);; let square_allxs = map square xs;; let square_all = map square;;

  24. Eliminating the Sugar in Map let recmap fxs = match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl);; let recmap = (fun f -> (fun xs -> match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl)));;

  25. So map square: let recmap = (fun f -> (fun xs -> match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl)));; let square_all = map square

  26. …is equivalent to let recmap = (fun f -> (fun xs -> match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl)));; let square_all = (fun xs -> match xswith | [] -> [] | hd::tl -> (square hd)::(map square tl))

  27. …which is equivalent to let square_all = (fun xs -> match xswith | [] -> [] | hd::tl -> (fhd)::(map square tl)) let square_allxs = match xswith | [] -> [] | hd::tl -> (fhd)::(map square tl);; Moral: you can get Ocaml to write code for you by simply using higher-order functions like map.

  28. How would you simplify sum & prod? let recreduce fuxs = match xswith | [] -> u | hd::tl -> fhd (reduce futl);; let sum xs = reduce add 0 xs ;; let prod xs = reduce mul 1 xs ;;

  29. How would you simply sum & prod? let recreduce fuxs = match xswith | [] -> u | hd::tl -> fhd (reduce futl);; let sum = reduce add 0;; let prod = reduce mul 1;;

  30. Actually: Stupid parser thinks this is a comment if we leave out the space! The built in operators like +, -, *, /, etc. are all curried functions. That is, they types int -> int -> int. The parser expects that we’ll put them in between two arguments (e.g., x + y). Unless we wrap parentheses around them like (+) xy. So, for example, we can write: let sum = reduce (+) 0;; let prod = reduce ( *) 1;;

  31. Recall: let recreduce = fun f -> (fun u -> (fun xs -> match xswith | [] -> u | hd::tl -> fhd (reduce futl)));; let sum = reduce (+) 0;; let prod = reduce ( *) 1;;

  32. Putting Parentheses in: let recreduce = fun f -> (fun u -> (fun xs -> match xswith | [] -> u | hd::tl -> fhd (reduce futl)));; let sum = (reduce (+)) 0;; let prod = (reduce ( *)) 1;;

  33. Here’s an annoying thing: let recmap (f:int->int) (xs:int list) : int list = match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl);; What if I want to increment a list of floats?

  34. Here’s an annoying thing: let recmap (f:int->int) (xs:int list) : int list = match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl);; What if I want to increment a list of floats? Alas, I can’t just call this map. It works on ints! let recmapfloat (f:float->float) (xs:float list) : float list = match xswith | [] -> [] | hd::tl -> (fhd)::(mapfloatftl);;

  35. Turns out: let recmap fxs = match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl);; map (fun x -> x + 1) [1; 2; 3; 4] ;; map (fun x -> x +. 2.0) [3.1415; 2.718; 42.0] ;; map String.uppercase [“greg”; “victor”; “joe”] ;;

  36. Type of the undecorated map? let recmap fxs = match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl);; (‘a -> ‘b) -> ‘a list -> ‘b list

  37. Type of the undecorated map? We often use greek letters like a or b to represent type variables. let recmap fxs = match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl);; (‘a -> ‘b) -> ‘a list -> ‘b list Read as: for any types ‘a and ‘b, if you give map a function from ‘a to ‘b, it will return a function which when given a list of ‘a values, returns a list of ‘b values.

  38. We can say this explicitly: let recmap (f:’a->’b) (xs:’a list) : ‘b list = match xswith | [] -> [] | hd::tl -> (fhd)::(mapftl);; The Ocaml compiler is smart enough to figure out that this is the most general type that you can assign to the code. We say map is polymorphic in the types ‘a and ‘b – just a fancy way to say map can be used on many types.

  39. More realistic polymorphic functions: let recmerge (lt:’a->’a->bool) (xs:’a list) (ys:’a list) : ‘a list = match (xs,ys) with | ([],_) -> ys | (_,[]) -> xs | (x::xst, y::yst) -> if ltxythen x::(mergeltxstys) else y::(mergeltxsyst) ;; let rec split (xs:’a list) (ys:’a list) (zs:’a list) : ‘a list * ‘a list = matchxswith | [] -> (ys, zs) | x::rest -> split rest zs (x::ys);; let recmergesort (lt:’a->’a->bool) (xs:’a list) : ‘a list = match xswith | ([] | _::[]) -> xs | _ -> let (first,second) = split xs [] [] in merge lt (mergesortlt first) (mergesortlt second) ;;

  40. More realistic polymorphic functions: let recmergesort (lt:’a->’a->bool) (xs:’a list) : ‘a list = match xswith | ([] | _::[]) -> xs | _ -> let (first,second) = split xs [] [] in merge lt (mergesort first) (mergesort second) mergesort (<) [3;2;7;1] = [1;2;3;7] mergesort (>) [2.718; 3.1415; 42.0] = [42.0 ; 3.1415; 2.718] mergesort (fun xy -> String.comparexy < 0) [“Victor”; “Greg”; “Joe”] = [“Greg”; “Joe”; “Victor”] let int_sort = mergesort (<) ;; let int_sort_down = mergesort (>);; let string_sort = mergesort (fun xy -> String.comparexy < 0) ;;

  41. Another Interesting Function: let comp fgx = f (gx) ;; let mystery = comp (add 1) square ;;

  42. Another Interesting Function: let comp fg = (fun x -> f (gx)) ;; let mystery = comp (add 1) square ;; let mystery = (fun x -> (add 1 (square x)));; let mystery x = add 1 (square x) ;;

  43. Optimization: let comp fg = (fun x -> f (gx)) ;; map f (map g [x1;x2;…;xn]) = map f [g x1; g x2; … ; gxn] = [f(g x1) ; f(g x2) ; … ; f(gxn)]

  44. Optimization: let comp fg = (fun x -> f (gx)) ;; map f (map g) [x1;x2;…;xn] = map f [g x1; g x2; … ; gxn] = [f(g x1) ; f(g x2) ; … ; f(gxn)] map (comp fg) [x1;x2;…;xn] = [f(g x1); f(g x2); … ; f(gxn)] Go from two passes over the loop to one. This is what query optimizers for SQL do.

  45. What is the type of comp? let comp fg = (fun x -> f (gx)) ;; (‘b -> ‘c) -> (‘a -> ‘b) -> ‘a -> ‘c

  46. How about reduce? let recreduce fuxs = match xswith | [] -> u | hd::tl -> fhd (reduce futl);; What’s the most general type of reduce?

  47. How about reduce? Based on the patterns, we know xs must be a (‘a list) for some type ‘a. let recreduce fuxs = match xswith | [] -> u | hd::tl -> fhd (reduce futl);; What’s the most general type of reduce?

  48. How about reduce? let recreduce fu (xs: ‘a list) = match xswith | [] -> u | hd::tl -> fhd (reduce futl);; What’s the most general type of reduce?

  49. How about reduce? f is called so it must be a function of two arguments. let recreduce fu (xs: ‘a list) = match xswith | [] -> u | hd::tl -> fhd (reduce futl);; What’s the most general type of reduce?

  50. How about reduce? let recreduce (f:? -> ? -> ?) u (xs: ‘a list) = match xswith | [] -> u | hd::tl -> fhd (reduce futl);; What’s the most general type of reduce?

More Related