COP 4020 Lecture -*- Outline -*- * Procedural Abstraction (Higher-Order Programming, 3.6) ------------------------------------------ def: the *order* of a function F is 1+(maximum order of F's arguments), where the order of a non-function argument is 0. def: A function is *higher-order* if its order is greater than 1. Examples: Add1: Map: }:S>}: > ------------------------------------------ ** Basic Operations (3.6.1) Q: What are the 4 basic operations of higher-order programming? ------------------------------------------ 4 KINDS OF HIGHER-ORDER PROGRAMMING ------------------------------------------ ... 1. procedural abstraction: converting statements/expressions to procedures/functions (Liskov & Guttag's abstraction by parameterization) 2. genericity: passing procedures/functions as arguments (abstracting out statements/expressions, not just data) 3. instantiation: returning procedure/function values as results (creation of new procedures/functions) 4. embedding: putting procedures/functions in data structures *** procedural abstraction Q: How can you make a statement S into a procedure? proc {$} S end this freezes execution (makes a closure, or thunk) Q: Suppose we want two variants of a procedure that are similar, but differ a bit? Use arguments *** genericity **** for iteration (3.2.4) ------------------------------------------ ABSTRACTION OF ITERATION (3.2.4) Consider fun {SumFromToIter JnR} J#R=JnR in if I > J then R else {SumFromToIter J-1#R+J} end end fun {SqrtIter Guess} if {GoodEnough Guess} then Guess else {SqrtIter {Improve Guess}} end end What do they have in common? How do they differ? Can we make the differences arguments? ------------------------------------------ ... general outline, returning part of state (R or Guess) Passing new value to state in recursion ... test for being done I > J or GoodEnough, how to extract the result from the state, how to transform the state ... yes % curried fun {Iterate IsDone Extract Transform} fun {Loop S} if {IsDone S} then {Extract S} else {Loop {Transform S}} end end end If you don't like to produce a function directly, then you can write... % compare to page 123 fun {Iterate2 IsDone Extract Transform S} if {IsDone S} then {Extract S} else {Iterate2 IsDone Extract Transform {Transform S}} end end % avoiding passing unchanging arguments fun {Iterate3 IsDone Extract Transform S} fun {Loop S} if {IsDone S} then {Extract S} else {Loop {Transform S}} end end in {Loop S} end How would you write SqrtIter using Iterate? SumFromToIter? SqrtIter = {Iterate GoodEnough fun {$ X} X end Improve} SumFromToIter = {Iterate fun {$ JnR} J#_=JnR in I > J end fun {$ JnR} _#R=JnR in R end fun {$ JnR} J#R=JnR in J-1#R+J end } **** for lists ------------------------------------------ ABSTRACTING A COMMON PATTERN fun {Sum Ls} case Ls of E|Es then E+{Sum Es} else 0 end end fun {Product Ls} case Ls of E|Es then E*{Product Es} else 1 end end ------------------------------------------ Q: What are the parts specific to computing the sum? the product? identify the common parts of this pattern, and pass the changing parts as arguments fun {FoldR Ls Op Z} case Ls of E|Es then {Op E {FoldR Es Op Z Es}} else Z end end Q: How can you write Sum and Product using FoldR? fun {Sum Ls} {FoldR Ls fun {$ X Y} X+Y end 0} end fun {Product Ls} {FoldR Ls Number.'*' 0} end ------------------------------------------ FOLDR AS A LIST HOMOMORPHISM {FoldR 1|(2|(3|nil)) Number.'-' 0} == 1-(2-(3-0)) In general: {FoldR '|'(X1'|'(X2'|'(...'|'(Xn nil)...))) == {F X1 {F X2 ... {F Xn U} ...}}} ------------------------------------------ Note how this is a homomorphism, translating | to - and nil to 0. in general Note: FoldL has the behavior {FoldL X1|X2|...|Xn|nil F U} == {F ... {F {F U X1} X2} ... Xn} ------------------------------------------ FOR YOU TO DO Using FoldR: S}: S> Append: }: > and "andthen" write: Concat: >}: > {Concat nil} == nil {Concat [[4 5 6] nil} == [4 5 6] {Concat [[1] nil [2 3] [4 5 6] nil]} == [1 2 3 4 5 6] All: }: Boolean> {All [true true false]} == false {All [true true]} == true {All nil} == true ------------------------------------------ See FoldRExamples(Test).oz The book also does a generic version of MergeSort. **** for trees Compare to section 3.6.4.2 ------------------------------------------ FOR YOU TO DO > ::= leaf | tree( key: value: T left: > right: > ) Generalize: fun {Preorder T} case T of leaf then nil [] tree(key: L value: V left: Left right: Right) then (L#V) | {Append {Preorder Left} {Preorder Right}} end end fun {IncTree T} case T of leaf then leaf [] tree(key: L value: V left: Left right: Right) then tree(key: L value: 1+V left: {IncTree Left} right: {IncTree Right}) end end WRITE FOLDTREE AS A GENERALIZATION ------------------------------------------ see TreeExamples(Test).oz If necessary, to make the pattern clearer, can rewrite the above as: fun {Preorder T} case T of leaf then nil [] tree(key: L value: V left: Left right: Right) then fun {Combine L V Left Right} (L#V) | {Append Left Right} end in {Combine L V {Preorder Left} {Preorder Right}} end end fun {IncTree T} case T of leaf then leaf [] tree(key: L value: V left: Left right: Right) then fun {Combine L V Left Right} tree(key: L value: 1+V left: Left right: Right) end in {Combine L V {IncTree Left} {IncTree Right}} end end Then identify the changing parts, and make those parameters: fun {FoldTree T Combine Base} case T of leaf then Base [] tree(key: L value: V left: Left right: Right) then {Combine L V {FoldTree Left Combine Base} {FoldTree Right Combine Base}} end end Q: How would you use the result to write Preorder and IncTree? fun {Preorder T} {FoldTree T fun {$ L V Left Right} (L#V) | {Append Left Right} end nil} end fun {IncTree T} {FoldTree T fun {$ L V Left Right} tree(key: L value: 1+V left: Left right: Right) end leaf} end *** instantiation (currying) (3.6.6) ------------------------------------------ INSTANTIATION or RULES THAT PRODUCE RULES Aka: factories, generators, curried functions Examples: fun {MakeSort Comparison} fun {$ Ls} {Sort Ls Comparison} end end Use of MakeSort: SortGT = {MakeSort fun {$ X Y} X > Y end} {SortGT List1} {SortGT List2} ... ------------------------------------------ Q: How could we do this for the tree example? fun {FoldTree Combine Base} fun {$ T} case T of leaf then Base [] tree(key: L value: V left: Left right: Right) then {Combine L V {{FoldTree Combine Base} Left} {{FoldTree Combine Base} Right}} end end end Q: How would you use the that to write Preorder and IncTree? Preorder = {FoldTree fun {$ L V Left Right} (L#V) | {Append Left Right} end nil} IncTree = {FoldTree fun {$ L V Left Right} tree(key: L value: 1+V left: Left right: Right) end leaf} Now let's look at how to write such functions directly, from examples Want to show you a 2 step process for writing them: ------------------------------------------ AN EXAMPLE: COMPOSE Write the function Compose such that: {{Compose Head Tail} [a b c]} == {Head {Tail [a b c]}} == {Head [b c]} == b {{Compose Not IsEmpty} nil} == {Not {IsEmpty nil}} == false (The examples assume that: fun {Head L} X|_=L in X end fun {Tail L} _|Xs=L in Xs end fun {IsEmpty L} L == nil end ) How to write this: ------------------------------------------ Show how to generalize these examples to get the answer. First generalize the examples: in general {{Compose F G} X} == {F {G X}} So {Compose F G} is a function of one argument, X, so it must be fun {$ X} {F {G X}} end But Compose itself is a procedure, of two arguments, F and G, and when called it returns this other procedure, so... fun {Compose F G} fun {$ X} {F {G X}} end end ------------------------------------------ SUMMARY OF STEPS FOR MAKING A HIGHER-ORDER FUNCTION 1. Starting from examples, name the roles 2. Generalize the result expression, using role names, write it down 3. Wrap fun declarations around it, corresponding to the arguments ------------------------------------------ ------------------------------------------ FOR YOU TO DO Write a procedure Twice Twice: }: > Examples: {{Twice Not} true} == {Not {Not true}} == true {{Twice fun {$ N} N+1 end} 3} == {{fun {$ N} N+1 end} {{fun {$ N} N+1 end} 3}} == 5 ------------------------------------------ Hints: What's the general formula? {{Twice F} X} == {F {F X}} *** embedding ------------------------------------------ EMBEDDING Putting closures in data is useful for: - explicit lazy evaluation - modules = records of operations - components, which return modules - manipulating actions as data (e.g., in testing) ------------------------------------------ This is also the basic OO paradigm ------------------------------------------ EXAMPLE: INFINITE SEQUENCES Write the following functions to implement Repeat: }: > Generate: : }: > Nth: }: > Add: }: > For example: declare Ones = {Repeat 1} Halves = {Generate fun {$ N} 1.0/{IntToFloat {Pow 2 N}} end} {Show {Nth Ones 33}} {Show [{Nth Halves 0} {Nth Halves 1} {Nth Halves 2} {Nth Halves 3} {Nth Halves 30}]} shows: 1 [1.0 0.5 0.25 0.125 9.3132e~010] ------------------------------------------ See SeqTest.oz Q: How can we represent something infinite in a computer? As a rule, i.e., as a function. Here we will use rules of type }: > to represent the type The idea is that, e.g., fun {$ N} N*N end represents the sequence 0, 1, 4, 9, 16, 25, ... declare fun {Repeat Num} fun {$ N} Num end end fun {Generate F} fun {$ N} {F N} end end fun {Nth Seq N} {Seq N} end fun {Add First Second} fun {$ N} {Nth First N} + {Nth Second N} end end ** Loop abstractions (3.6.2-3) *** FoldL and other loops over lists (3.6.2) These are examples of genericity Q: Does it do any good in the declarative model to run an action {A I} for each integer I in 1 to 10? No ------------------------------------------ FOLDL {FoldL X1|X2|...|Xn|nil F U} == {F ... {F {F U X1} X2} ... Xn} {FoldL nil F U} == U fun {FoldL L F U} case L of nil then U [] X|L2 then {FoldL L2 F {F U X}} end end ------------------------------------------ FoldL accumulates from the left, so is opposite of homomorphic, vs. FoldR Note that the function takes its arguments in the opposite order of FoldR Q: Is FoldL iterative? yes! *** linguistic support for loops (3.6.3) Q: What's the difference between a declarative and imperative loop? In the declarative version, each run of the body has a different (new) variable ------------------------------------------ FOR LOOPS IN OZ Simple counting: for I in A .. B do {Stmt I} end With step S: for I in A .. B; S do {Stmt I} end For lists: for E in L do {Stmt E} end With pattern match: for foo(X Y) in L do {Stmt X Y} end FORM WITH COLLECT: USEFUL IN DECLARATIVE MODEL Collection of results: for Elem in Ls collect: Results do {Results {F Elem}} end for foo(X _) in L collect: Xs do {Xs X} done ------------------------------------------ The collect: form is the only one really useful in the declarative model, with collect: the result is the final value of the variable declared after collect: Also with collect: the for loop is an *expression* otherwise it's a *statement* On the other hand, in all cases the body is a statement. Q: How would you write a function that returns all the numbers in a list LoN that are strictly greater than N? fun {GreaterThan LoN N} for E in LoN collect: Bigger do if E > N then {Bigger E} end end end **** for loop semantics (skip) See documentation for more ------------------------------------------ FOR LOOP DESUGARING for in collect: do end ==> local in local Body = proc {$ } end in {ForLoop Body} end end where Body is fresh and ForLoop is defined by: % ForLoop: > % }> % }>}: % >> fun {ForLoop Exp ?Name Body} local Z Res = Z#Z % the automatically generated proc Name = proc {$ X} local _#ResEnd = Res in ResEnd = X|_ end end % processing each list element proc {Loop Ls} case Ls of H|T then {Body H} {Loop T} else skip end end in {Loop Exp} % convert the difference list to a list local ResFront#ResEnd = Res in ResEnd = nil ResFront end end end ------------------------------------------ see ForLoop.oz Q: What variables can be used in ? the and the variables declared in Q: Why does Body have to be a proc? because the call to Body is used as a statement