Abstracting Repetitions Fold Operations Abstracting Repetitions cs7120(Prasad) L6-FOLD
Motivation : Appending lists lappend (link) generalization of binary append to appending an arbitrary number of lists fun lappend [] = [] | lappend (s::ss) = s @ (lappend ss) lappend : ’a list list -> ’a list cs7120(Prasad) L6-FOLD
Properties (Identities) (map f) o lappend = lappend o (map (map f)) filter p (xs @ ys) = (filter p xs) @ (filter p ys) (filter p) o lappend = lappend o (map (filter p)) Efficiency issues and transformations; cf. associative, commutative and distributive laws; Mathematical foundation for code optimization cs7120(Prasad) L6-FOLD
Fold Operators Generalize binary operators to n-ary functions (list functions). Abstract specific patterns of recursion / looping constructs. Potential for optimization of special forms. foldl, accumulate, revfold, … foldr, reduce, fold, … foldl1 foldr1 Operator vs infix function vs function cs7120(Prasad) L6-FOLD
Foldr (SML97) foldr f a [x1,x2,…,xn] = f( x1 , f(x2, …, f(xn,a)...)) fun foldr f a [] = a | foldr f a (x::xs) = f (x, foldr f a xs) foldr : (’a*’b -> ’b) ->’b -> ’a list -> ’b foldr (op +) 10 [1,2,3] = 16 Normally, f is associative and a is identity wrt f. foldr (op +) a [x1,x2,x3] = ( x1 + ( x2 + ( x3 + a) ) ) ) foldr (op ::) [1,0] [4,3,2] = [4,3,2,1,0] cs7120(Prasad) L6-FOLD
Examples foldr (op +) a [x1,x2,x3] = ( x1 + ( x2 + ( x3 + a) ) ) ) [4,3,2,1,0] cs7120(Prasad) L6-FOLD
(cont’d) fold in OldML (not suited for partial eval.) fold : (’a*’b -> ’b) -> ’a list ->’b -> ’b foldr in Bird and Wadler (Curried func.) reduce in Reade foldr : (’a -> ’b -> ’b) -> ’b -> ’a list -> ’b cs7120(Prasad) L6-FOLD
Foldl (SML97) foldl f a [x1,x2,…,xn] = f(xn,f(…,f(x2,f(x1,a))…)) fun foldl f a [] = a | foldl f a (x::xs) = foldl f (f(x,a)) xs foldl : (’a*’b -> ’b) ->’b -> ’a list -> ’b foldl (op * ) 10 [1,2,3] = 60 Normally, f is associative and a is identity wrt f. foldl (op *) [x1,x2,…,xn] a = (xn * (…*(x2 * (x1 * a))...)) foldl (op ::) [1,0] [4,3,2] = [2,3,4,1,0] foldl (op @) [0] [[1],[2],[3],[4]] = [4,3,2,1,0] fun foldl f a [] = a | foldl f a (x::xs) = foldl f (f(a,x)) xs foldl : (‘b*‘a -> ‘b) ->‘b -> ‘a list -> ‘b cs7120(Prasad) L6-FOLD
foldl (op @) [0] [[1],[2],[3],[4]] Examples foldl (op *) a [x1,x2,…,xn] = (xn * (…*(x2 * (x1 * a))...)) foldl (op @) [0] [[1],[2],[3],[4]] [4,3,2,1,0] foldl (op ::) [1,0] [4,3,2] [2,3,4,1,0] cs7120(Prasad) L6-FOLD
(cont’d) revfold in OldML (not suited for partial eval.) revfold : (’b*’a -> ’b) -> ’a list ->’b -> ’b foldl in Bird and Wadler (Curried func.) accumulate in Reade foldl : (’b -> ’a -> ’b) -> ’b -> ’a list ->’b cs7120(Prasad) L6-FOLD
Examples fun pack ds = foldl (fn (d,v)=> d+v*10) 0 ds fun packNot ds = foldl (fn (d,v)=> d*10+v) 0 ds packNot [1,2,3,4] = 100 fun packNott ds = foldr (fn (d,v)=> d+v*10) 0 ds packNott [1,2,3,4] = 4321 Horner’s rule cs7120(Prasad) L6-FOLD
foldr (fn (x,xs) => x::xs) [] lis myId [1,2,3,4] = [1,2,3,4] fun myId lis = foldr (fn (x,xs) => x::xs) [] lis myId [1,2,3,4] = [1,2,3,4] fun myRev lis = foldl (fn (x,xs) => x::xs) [] lis myRev [1,2,3,4] = [4,3,2,1] Foldr preserves ordering, foldl reverses it. cs7120(Prasad) L6-FOLD
foldr (fn (x,xs) => if p x then x::xs else xs) [] lis; fun filter p lis = foldr (fn (x,xs) => if p x then x::xs else xs) [] lis; filter (fn x => x = 2) [1,2,3,2] fun filterNeg p lis = foldl (fn (x,xs) => if p x then xs else xs@[x]) filterNeg (fn x => true) [“a”,”b”,”a”] These definitions inspect each element and collect their contribution. filterNeg p = filter (not p) Basically these definitions illustrate that both foldl and foldr can be used to define filter. Fun filter p = foldl (fn (x,r) => if p x then r@[x] else r) [] ; Examples illustrate atypical uses!!! cs7120(Prasad) L6-FOLD
foldr (fn (x,xs) => if p x then x::xs else []) [] lis; fun takewhile p lis = foldr (fn (x,xs) => if p x then x::xs else []) [] lis; takewhile (fn x => x = 2) [2,2,3,1,2] fun dropwhile p lis = foldl (fn (x,xs) => if null xs andalso p x then [] else xs@[x]) dropwhile (fn x => true) [1,2,3] takewhile definition using foldr inspects each element of the list from the right and drops the tail if the head element fails the test. It accomplishes the task inefficiently compared to the recursive definition that scans the list from the left. However, contrary to expectation, defining takewhile using foldl is not simple we need to retain the entire suffix irrespective of the value of the element. Similarly, dropwhile has a simple definition in terms of foldl, but not in terms of foldr. fun takewhileX p = foldl version needs additional marker which will restrict the use of the function. fun dropwhileX p = foldr version is not possible because the decision to drop an element in the list depends on the unseen part. cs7120(Prasad) L6-FOLD
Generalizing Operators without identity element E.g., max, min, etc for which basis clause (for []) cannot be defined. fun foldl_1 f (x::xs) = foldl f x xs; fun foldr_1 f (x::[]) = x | foldr_1 f (x::y::ys) = f x (foldr_1 f (y::ys)) cs7120(Prasad) L6-FOLD
Laws : Identities If f is a binary function that is associative and a is an identity w.r.t. f, then foldr f a xs = foldl f a (rev xs) foldr (op @) [] [[1,2],[3,4],[5]] = [1,2,3,4,5] foldl (op @) [] (rev [[1,2],[3,4],[5]]) foldl (op @) [] [[1,2],[3,4],[5]] = [5,3,4,1,2] foldr (op +) 0 [1,2,3] = 6 foldl (op +) 0 [1,2,3] = 6 foldl (op +) 0 (rev [1,2,3]) = 6 Reverse not required because of commutativity of +. cs7120(Prasad) L6-FOLD
Laws : Identities If f is a binary function that is commutative and a is an identity w.r.t. f, then foldr f a xs = foldl f a xs foldr (op * ) 1 [1,2,3] = 6 foldl (op * ) 1 [1,2,3] = 6 foldr (fn(b,v)=> b andalso v) true [false] = false foldl (fn(b,v)=> b andalso v) true [false] foldr (op +) 0 [1,2,3] = 6 foldl (op +) 0 [1,2,3] = 6 foldl (op +) 0 (rev [1,2,3]) = 6 Reverse not required because of commutativity of +. cs7120(Prasad) L6-FOLD
Comparing foldl and foldr. foldl (op +) 0 [1,2,3] = foldl (op +) 1 [2,3] = foldl (op +) 3 [3] = foldl (op +) 6 [] = 6 (foldl: Efficient) (Tail Recursive) foldr (op +) 0 [1,2,3] =1+foldr (op +) 0 [2,3] =3 + foldr (op +) 0 [3] =6 + foldr (op +) 0 [] foldl and t [t,f,t] = foldl and t [f,t] = foldl and f [t] = foldl and f [] = false (foldr: Efficient) (Short-circuit evaluation) foldr and t [t,f,t] = and t (foldr and t [f,t] = and f (foldr and t [t]) foldl + call by value : tail-recursive optimization (Accumulator technique) foldr + lazy evaluation : short-circuit operation cs7120(Prasad) L6-FOLD
scan (processing prefixes) scan f a [x1,x2,…,xn] = [a,(f a x1),(f (f a x1) x2),…, (f … (f (f a x1) x2) …xn)] fun scan f a [] = [a] | scan f a xs = let val t = (scan f a (init xs)) in t @ [(f (last t) (last xs))] end; scan : (‘a -> ‘b -> ‘a) -> ‘a -> ‘b list -> ‘a list fun scanv f a lis = foldl (fn (x,v) => v @ [(f ((last v), x) )] ) [a] lis; scanv : (‘a * ‘b -> ‘a) -> ‘a -> ‘b list -> ‘a list scanv (op +) 0 [1,2,3,4,5] = [0,1,3,6,10,15] cs7120(Prasad) L6-FOLD