Poly-HO!
polymorphic,higher-orderprogramming
COS326DavidWalker
PrincetonUniversityslidescopyright2013-2015DavidWalkerandAndrewW.Appel
permissiongrantedtoreusetheseslidesfornon-commercialeducaJonalpurposes
SomeDesign&CodingRules
2
SomeDesign&CodingRules
3
• Lazinesscanbeareallygoodforceindesign.• Neverwritethesamecodetwice.
– factoroutthecommonbitsintoareusableprocedure.– beOer,usesomeoneelse’s(well-tested,well-documented,andwell-maintained)procedure.
• Whyisthisagoodidea?– whydon’twejustcut-and-pastesnippetsofcodeusingtheeditorinsteadofabstracJngthemintoprocedures?
SomeDesign&CodingRules
4
• Lazinesscanbeareallygoodforceindesign.• Neverwritethesamecodetwice.
– factoroutthecommonbitsintoareusableprocedure.– beOer,usesomeoneelse’s(well-tested,well-documented,andwell-maintained)procedure.
• Whyisthisagoodidea?– whydon’twejustcut-and-pastesnippetsofcodeusingtheeditorinsteadofabstracJngthemintoprocedures?
– findandfixabuginonecopy,havetofixinallofthem.– decidetochangethefuncJonality,havetotrackdownalloftheplaceswhereitgetsused.
FactoringCodeinOCaml
5
ConsiderthesedefiniJons:
let rec inc_all (xs:int list) : int list = match xs with | [] -> [] | hd::tl -> (hd+1)::(inc_all tl) let rec square_all (xs:int list) : int list = match xs with | [] -> [] | hd::tl -> (hd*hd)::(square_all tl)
FactoringCodeinOCaml
6
ConsiderthesedefiniJons:ThecodeisalmostidenJcal–factoritout!
let rec inc_all (xs:int list) : int list = match xs with | [] -> [] | hd::tl -> (hd+1)::(inc_all tl) let rec square_all (xs:int list) : int list = match xs with | [] -> [] | hd::tl -> (hd*hd)::(square_all tl)
FactoringCodeinOCaml
7
Ahigher-orderfuncJoncapturestherecursionpaOern:
let rec map (f:int->int) (xs:int list) : int list = match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl);;
FactoringCodeinOCaml
8
Ahigher-orderfuncJoncapturestherecursionpaOern:UsesofthefuncJon:
let rec map (f:int->int) (xs:int list) : int list = match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl);;
let inc x = x+1;; let inc_all xs = map inc xs;;
FactoringCodeinOCaml
9
Ahigher-orderfuncJoncapturestherecursionpaOern:UsesofthefuncJon:
let rec map (f:int->int) (xs:int list) : int list = match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl);;
let inc x = x+1;; let inc_all xs = map inc xs;; let square y = y*y;; let square_all xs = map square xs;;
WriJngliOlefuncJonslikeincjustsowecallmapisapain.
FactoringCodeinOCaml
10
Ahigher-orderfuncJoncapturestherecursionpaOern:UsesofthefuncJon:
let rec map (f:int->int) (xs:int list) : int list = match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl);;
let inc_all xs = map (fun x -> x + 1) xs;; let square_all xs = map (fun y -> y * y) xs;;
WecanuseananonymousfuncJoninstead. Originally,
ChurchwrotethisfuncJon
usingλ insteadoffun:
(λx.x+1)or(λx.x*x)
Anotherexample
11
let rec sum (xs:int list) : int = match xs with | [] -> 0 | hd::tl -> hd + (sum tl) ;; let rec prod (xs:int list) : int = match xs with | [] -> 1 | hd::tl -> hd * (prod tl) ;; Goal:CreateafuncJoncalledreducethat
whensuppliedwithafewargumentscanimplementbothsumandprod.Definesum2andprod2usingreduce.(Tryit)
Goal:Ifyoufinishearly,usemapandreducetogethertofindthesumofthesquaresoftheelementsofalist.(Tryit)
Agenericreducer
12
let add x y = x + y;; let mul x y = x * y;; let rec reduce (f:int->int->int) (u:int) (xs:int list) : int = match xs with | [] -> u | hd::tl -> f hd (reduce f u tl);; let sum xs = reduce add 0 xs ;; let prod xs = reduce mul 1 xs ;;
UsingAnonymousFuncJons
13
let rec reduce (f:int->int->int) (u:int) (xs:int list) : int = match xs with | [] -> u | hd::tl -> f hd (reduce f u tl);; let sum xs = reduce (fun x y -> x+y) 0 xs ;; let prod xs = reduce (fun x y -> x*y) 1 xs ;;
UsingAnonymousFuncJons
14
let rec reduce (f:int->int->int) (u:int) (xs:int list) : int = match xs with | [] -> u | hd::tl -> f hd (reduce f u tl);; let sum xs = reduce (fun x y -> x+y) 0 xs ;; let prod xs = reduce (fun x y -> x*y) 1 xs ;; let sum_of_squares xs = sum (map (fun x -> x * x) xs) let pairify xs = map (fun x -> (x,x)) xs
MoreonAnonymousFuncJons
15
(nonrecursive)FuncJondeclaraJonsareactuallyabbreviaJons:aresyntac6csugarfor:So,func6onsarevalueswecanbindtoavariable,justlike3or“moo”ortrue.OCamlobeystheprincipleoforthogonallanguagedesign.
let square x = x*x ;; let add x y = x+y ;;
let square = (fun x -> x*x) ;; let add = (fun x y -> x+y) ;;
Oneargument,oneresult
16
Simplifyingfurther:isshorthandfor:Thatis,addisafuncJonwhich:
– whengivenavaluex,returnsafunc6on(funy->x+y)which:• whengivenavaluey,returnsx+y.
let add = (fun x y -> x+y)
let add = (fun x -> (fun y -> x+y))
CurriedFuncJons
17
fun x -> (fun y -> x+y) (* curried *) fun x y -> x + y (* curried *) fun (x,y) -> x+y (* uncurried *) Currying:encodingamulJ-argument
funcJonusingnested,higher-orderfuncJons.
NamedagerthelogicianHaskellB.Curry(1950s).
– wastryingtofindminimallogicsthatarepowerfulenoughtoencodetradiJonallogics.
– mucheasiertoprovesomethingaboutalogicwith3connecJvesthanonewith20.
– theideastranslatedirectlytomath(set&categorytheory)aswellastocomputerscience.
– (actually,MosesSchönfinkeldidsomeofthisin1924)– (thankfully,wedon'thavetotalkaboutSchönfinkelledfuncJons)
Whatisthetypeofadd?
18
Add’stypeis:whichwecanwriteas:Thatis,thearrowtypeisright-associaJve.
let add = (fun x -> (fun y -> x+y))
int -> (int -> int)
int -> int -> int
What’ssogoodaboutCurrying?
19
InaddiJontosimplifyingthelanguage(orthogonaldesign),curryingfuncJonssothattheyonlytakeoneargumentleadstotwomajorwins:
1. Wecanpar6allyapplyafuncJon.2. WecanmoreeasilycomposefuncJons.
ParJalApplicaJon
20
CurriedfuncJonsallowdefsofnew,parJallyappliedfuncJons:EquivalenttowriJng:whichisequivalenttowriJng:also:
let add = (fun x -> (fun y -> x+y)) ;;
let inc = add 1;;
let inc = (fun y -> 1+y);;
let inc y = 1+y;;
let inc2 = add 2;; let inc3 = add 3;;
SIMPLEREASONINGABOUTHIGHER-ORDERFUNCTIONS
ReasoningAboutDefiniJons
22
let rec map f xs = match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl);; let square_all = map square;;
let square_all ys = match ys with | [] -> [] | hd::tl -> (square hd)::(square_all tl) ;;
Wecanfactorthisprogram
intothisprogram:
amoreconciseandreadabledefiniJonofsquare_all(assumingwealreadyhaddefinedmap)
ReasoningAboutDefiniJons
23
Goal:RewritedefiniJonssomyprogramissimpler,easiertounderstand,moreconcise,…Ques6on:WhatarethereasoningprinciplesforrewriJngprogramswithoutbreakingthem?Forreasoningaboutthebehaviorofprograms?Abouttheequivalenceoftwoprograms?Iwantsomerulesfordoingsothatneverfail.
let square_all = map square;;
let square_all ys = match ys with | [] -> [] | hd::tl -> (square hd)::(square_all tl) ;;
SimpleEquaJonalReasoning
(fun x -> ... x ...) arg ... arg ...
let f = def let f x = (def) x
chosenamexwiselysoitdoesnotshadowothernamesusedindef
ifargisavalueor,whenexecuted,willalwaysterminatewithouteffectandproduceavalue
Rewrite2(SubsJtuJon):
Rewrite3(Eta-expansion):
iffhasafuncJontype
let f x = body let f = (fun x -> body)
Rewrite1(FuncJonde-sugaring):
==
==
==
roughly:alloccurrencesofxreplacedbyarg(thoughgeongthisexactlyrightisshockinglydifficult)
Eta-expansionisanexampleofLeibniz’slaw
let f = def let f = fun x -> (def)x
chosenamexwiselysoitdoesnotshadowothernamesusedindef
Rewrite3(Eta-expansion):
iffhasafuncJontype
==
GotriedWilhelmvonLeibnizGermanPhilosopher1646-1716
Leibniz’slaw:Ifeverypredicatepossessedbyxisalsopossessedbyyandviceversa,thenenJJesxandyareidenJcal.Frequentlyinvokedinmodernlogicandphilosophy.
EliminaJngtheSugarinMap
26
let rec map f xs = match xs with | [] -> []
| hd::tl -> (f hd)::(map f tl);;
EliminaJngtheSugarinMap
27
let rec map f xs = match xs with | [] -> []
| hd::tl -> (f hd)::(map f tl);;
let rec map = (fun f -> (fun xs -> match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl)));;
Considersquare_all
28
let rec map = (fun f -> (fun xs -> match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl)));;
let square_all = map square ;;
SubsJtutemapdefiniJonintosquare_all
29
let rec map = (fun f -> (fun xs -> match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl)));;
let square_all = (fun f ->
(fun xs ->
match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl)
)
) square ;;
SubsJtutemapdefiniJonintosquare_all
30
let rec map = (fun f -> (fun xs -> match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl)));;
let square_all = (fun f ->
(fun xs ->
match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl)
)
) square ;;
SubsJtutemapdefiniJonintosquare_all
31
let rec map = (fun f -> (fun xs -> match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl)));;
let square_all = (fun f ->
(fun xs ->
match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl)
)
) square ;;
SubsJtuteSquare
32
let rec map = (fun f -> (fun xs -> match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl)));;
let square_all = (
(fun xs ->
match xs with | [] -> [] | hd::tl -> (square hd)::(map square tl)
)
;;
argumentsquaresubsJtutedforparameterf
Expandingmapsquare
33
let rec map = (fun f -> (fun xs -> match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl)));;
let square_all ys =
(fun xs ->
match xs with | [] -> [] | hd::tl -> (square hd)::(map square tl)
) ys
;;
addargumentviaeta-expansion
Expandingmapsquare
34
let rec map = (fun f -> (fun xs -> match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl)));;
let square_all ys =
match ys with | [] -> [] | hd::tl -> (square hd)::(map square tl)
;;
subsJtuteagain(argumentysforparameterxs)
SoFar
35
let rec map f xs = match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl);;
let square_all xs = map square xs
let square_all ys = match ys with | [] -> [] | hd::tl -> (square hd)::(map square tl) ;;
proofbysimplerewriJngunrollsdefiniJononce
NextStep
36
let rec map f xs = match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl);;
let square_all xs = map square xs
let square_all ys = match ys with | [] -> [] | hd::tl -> (square hd)::(map square tl) ;;
let square_all ys = match ys with | [] -> [] | hd::tl -> (square hd)::(square_all tl) ;;
let square_all ys = match ys with | [] -> [] | hd::tl -> (square hd)::(map square tl) ;;
proofbyinduc6oneliminatesrecursivefuncJonmap
proofbysimplerewriJngunrollsdefiniJononce
Summary
37
Wesawthis:Isequivalenttothis:Moralofthestory• (1)OCaml’sHOT(higher-order,typed)funcJonscapturerecursionpaOerns• (2)wecanfigureoutwhatisgoingonbyequa6onalreasoning.• (3)...butwetypicallyneedtodoproofsbyinduc6ontoreasonabout
recursive(inducJve)funcJons
let rec map f xs = match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl);;
let square_all ys = map square
let square_all ys = match ys with | [] -> [] | hd::tl -> (square hd)::(map square tl) ;;
POLY-HO!
Here’sanannoyingthing
39
WhatifIwanttoincrementalistoffloats?Alas,Ican’tjustcallthismap.Itworksonints!
let rec map (f:int->int) (xs:int list) : int list = match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl);;
Here’sanannoyingthing
40
WhatifIwanttoincrementalistoffloats?Alas,Ican’tjustcallthismap.Itworksonints!
let rec map (f:int->int) (xs:int list) : int list = match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl);;
let rec mapfloat (f:float->float) (xs:float list) : float list = match xs with | [] -> [] | hd::tl -> (f hd)::(mapfloat f tl);;
Turnsout
41
let rec map f xs = match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl);; 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”] ;;
Typeoftheundecoratedmap?
42
let rec map f xs = match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl) ;; map : ('a -> 'b) -> 'a list -> 'b list
Typeoftheundecoratedmap?
43
Readas:foranytypes'aand'b,ifyougivemapafuncJonfrom'ato'b,itwillreturnafuncJonwhichwhengivenalistof'avalues,returnsalistof'bvalues.
let rec map f xs = match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl) ;; map : ('a -> 'b) -> 'a list -> 'b list
WeogenusegreekleOerslikeαorβtorepresenttype
variables.
Wecansaythisexplicitly
44
TheOCamlcompilerissmartenoughtofigureoutthatthisisthe
mostgeneraltypethatyoucanassigntothecode.Wesaymapispolymorphicinthetypes'aand'b–justafancy
waytosaymapcanbeusedonanytypes'aand'b.JavagenericsderivedfromML-stylepolymorphism(butadded
agerthefactandmorecomplicatedduetosubtyping)
let rec map (f:'a -> 'b) (xs:'a list) : 'b list = match xs with | [] -> [] | hd::tl -> (f hd)::(map f tl) ;; map : (‘a -> ‘b) -> ‘a list -> ‘b list
MorerealisJcpolymorphicfuncJons
45
let rec merge (lt:'a->'a->bool) (xs:'a list) (ys:'a list) : 'a list =
match (xs,ys) with | ([],_) -> ys
| (_,[]) -> xs
| (x::xst, y::yst) ->
if lt x y then x::(merge lt xst ys) else y::(merge lt xs yst) ;;
let rec split (xs:'a list) (ys:'a list) (zs:'a list) : 'a list * 'a list =
match xs with | [] -> (ys, zs) | x::rest -> split rest zs (x::ys) ;;
let rec mergesort (lt:'a->'a->bool) (xs:'a list) : 'a list = match xs with | ([] | _::[]) -> xs
| _ -> let (first,second) = split xs [] [] in merge lt (mergesort lt first) (mergesort lt second) ;;
MorerealisJcpolymorphicfuncJons
46
mergesort : ('a->'a->bool) -> 'a list -> 'a list
mergesort (<) [3;2;7;1]
== [1;2;3;7]
mergesort (>) [2.718; 3.1415; 42.0]
== [42.0 ; 3.1415; 2.718]
mergesort (fun x y -> String.compare x y < 0) [“Hi”; “Bi”] == [“Bi”; “Hi”]
let int_sort = mergesort (<) ;; let int_sort_down = mergesort (>) ;; let str_sort = mergesort (fun x y -> String.compare x y < 0) ;;
let mystery = fun x -> (add 1) (square x) ;;
AnotherInteresJngFuncJon
47
let comp f g x = f (g x) ;; let mystery = comp (add 1) square ;;
let comp = fun f -> (fun g -> (fun x -> f (g x))) ;; let mystery = comp (add 1) square ;;
let mystery = (fun f -> (fun g -> (fun x -> f (g x)))) (add 1) square ;;
let mystery x = add 1 (square x) ;;
OpJmizaJon
48
map f (map g [x1; x2; …; xn])
Whatdoesthisprogramdo?
Foreachelementofthelistx1,x2,x3...xn,itexecutesg,creaJng:
map f ([g x1; g x2; …; g xn])
Thenforeachelementofthelist[gx1,gx2,gx3...gxn],itexecutesf,creaJng:
[f (g x1); f (g x2); …; f (g xn)]
Isthereafasterway? Yes!(AndqueryopJmizersforSQLdoitforyou.)
map (comp f g) [x1; x2; ...; xn]
DeforestaJon
49
map f (map g [x1; x2; …; xn])
map (comp f g) [x1; x2; ...; xn]
ThiskindofopJmizaJonhasaname:
deforestaJon(becauseiteliminatesintermediatelistsand,um,trees…)
Whatisthetypeofcomp?
50
let comp f g x = f (g x) ;;
Whatisthetypeofcomp?
51
comp : ('b -> 'c) -> ('a -> 'b) -> ('a -> 'c)
let comp f g x = f (g x) ;;
Howaboutreduce?
52
let rec reduce f u xs = match xs with
| [] -> u | hd::tl -> f hd (reduce f u tl);;
What’sthemostgeneraltypeofreduce?
Howaboutreduce?
53
let rec reduce f u xs = match xs with
| [] -> u | hd::tl -> f hd (reduce f u tl);;
What’sthemostgeneraltypeofreduce?
BasedonthepaOerns,we
knowxsmustbea('alist)forsometype'a.
Howaboutreduce?
54
let rec reduce f u (xs: 'a list) = match xs with
| [] -> u | hd::tl -> f hd (reduce f u tl);;
What’sthemostgeneraltypeofreduce?
Howaboutreduce?
55
let rec reduce f u (xs: 'a list) = match xs with
| [] -> u | hd::tl -> f hd (reduce f u tl);;
What’sthemostgeneraltypeofreduce?
fiscalledsoitmustbea
funcJonoftwoarguments.
Howaboutreduce?
56
let rec reduce (f:? -> ? -> ?) u (xs: 'a list) = match xs with
| [] -> u | hd::tl -> f hd (reduce f u tl);;
What’sthemostgeneraltypeofreduce?
Howaboutreduce?
57
let rec reduce (f:? -> ? -> ?) u (xs: 'a list) = match xs with
| [] -> u | hd::tl -> f hd (reduce f u tl);;
What’sthemostgeneraltypeofreduce?
Furthermore,hdcamefromxs,sofmusttakean'avalueasitsfirst
argument.
Howaboutreduce?
58
let rec reduce (f:'a -> ? -> ?) u (xs: 'a list) = match xs with
| [] -> u | hd::tl -> f hd (reduce f u tl);;
What’sthemostgeneraltypeofreduce?
Howaboutreduce?
59
let rec reduce (f:'a -> ? -> ?) u (xs: 'a list) = match xs with
| [] -> u | hd::tl -> f hd (reduce f u tl);;
What’sthemostgeneraltypeofreduce?
Thesecondargumenttofmusthavethe
sametypeastheresultofreduce.Let’scallit'b.
Howaboutreduce?
60
let rec reduce (f:'a -> 'b -> ?) u (xs: 'a list) : 'b = match xs with
| [] -> u | hd::tl -> f hd (reduce f u tl);;
What’sthemostgeneraltypeofreduce?
Theresultoffmusthavethe
sametypeastheresultofreduce
overall:'b.
Howaboutreduce?
61
let rec reduce (f:'a -> 'b -> 'b) u (xs: 'a list) : 'b = match xs with
| [] -> u | hd::tl -> f hd (reduce f u tl);;
What’sthemostgeneraltypeofreduce?
Howaboutreduce?
62
let rec reduce (f:'a -> 'b -> ?) u (xs: 'a list) : 'b = match xs with
| [] -> u | hd::tl -> f hd (reduce f u tl);;
What’sthemostgeneraltypeofreduce?
Ifxsisempty,thenreduce
returnsu.Sou’stypemustbe'b.
Howaboutreduce?
63
let rec reduce (f:'a -> 'b -> 'b) (u:'b) (xs: 'a list) : 'b = match xs with
| [] -> u | hd::tl -> f hd (reduce f u tl);;
What’sthemostgeneraltypeofreduce?
Howaboutreduce?
64
let rec reduce (f:'a -> 'b -> 'b) (u:'b) (xs: 'a list) : 'b = match xs with
| [] -> u | hd::tl -> f hd (reduce f u tl);;
What’sthemostgeneraltypeofreduce?
('a -> 'b -> 'b) -> 'b -> 'a list -> 'b
TheListLibrary
65
NB:mapandreducearealreadydefinedintheListlibrary.– However,reduceiscalled“fold_right”.– (Goodbetthere’sa“fold_leg”too.)
I’llusereduceinsteadoffold_right,for3reasons:– AnalogywithGoogle’sMap/Reduce– Thelibrary’sargumentstofold_rightareinthe"wrong"order– Makestheexamplefitonaslide.
Summary
66
• Mapandreducearetwohigher-orderfunc6onsthatcapturevery,verycommonrecursionpaFerns
• Reduceisespeciallypowerful:– relatedtothe“visitorpaOern”ofOOlanguageslikeJava.– canimplementmostlist-processingfuncJonsusingit,includingthingslikecopy,append,filter,reverse,map,etc.
• Wecanwriteclear,terse,reusablecodebyexploiJng:– higher-orderfuncJons– anonymousfuncJons– first-classfuncJons– polymorphism
PracJceProblems
67
Usingmap,writeafuncJonthattakesalistofpairsofintegers,andproducesalistofthesumsofthepairs.
– e.g.,list_add[(1,3);(4,2);(3,0)]=[4;6;3]– Writelist_adddirectlyusingreduce.
Usingmap,writeafuncJonthattakesalistofpairsofintegers,andproducestheirquoJentifitexists.
– e.g.,list_div[(1,3);(4,2);(3,0)]=[Some0;Some2;None]– Writelist_divdirectlyusingreduce.
Usingreduce,writeafuncJonthattakesalistofopJonalintegers,andfiltersoutalloftheNone’s.
– e.g.,filter_none[Some0;Some2;None;Some1]=[0;2;1]– Whycan’twedirectlyusefilter?Howwouldyougeneralizefiltersothat
youcancomputefilter_none?AlternaJvely,rigupasoluJonusingfilter+map.
Usingreduce,writeafuncJontocomputethesumofsquaresofalistofnumbers.
– e.g.,sum_squares=[3,5,2]=38