Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire (cid:3) y z Erik Meijer Maarten Fokkinga Ross Paterson Abstract We develop a calculus for lazy functional programming based on recursion operators associated with data type de(cid:12)nitions. For these operators we derive various algebraic laws that are useful in deriving and manipulating programs. We shall show that all example functions in Bird and Wadler's \Introduction to Functional Programming" can be expressed using these operators. 1 Introduction Among the many styles and methodologies for the construction of computer programs the Squiggol style in our opinion deserves attention from the functional programming community. The overall goal of Squiggol is to calculate programs from their speci(cid:12)cation in the way a math- ematician calculates solutions to di(cid:11)erential equations, or uses arithmetic to solve numerical problems. It is not hard to state, prove and use laws for well-known operations such as addition, multi- plication and |at the function level| composition. It is, however, quite hard to state, prove and use laws for arbitrarily recursively de(cid:12)ned functions, mainly because it is di(cid:14)cult to refer to the recursion scheme in isolation. The algorithmic structure is obscured by using unstructured recursive de(cid:12)nitions. We crack this problem by treating various recursion schemes as separate higher order functions, giving each a notation of its own independent of the ingredients with which it constitutes a recursively de(cid:12)ned function. (cid:3) University of Nijmegen, Department of Informatics, Toernooiveld 6525 ED Nijmegen, e-mail: [email protected] y CWI, Amsterdam & University of Twente z Imperial College, London 1 This philosophy is similar in spirit to the `structured programming' methodology for imperative programming. The use of arbitrary goto's is abandoned in favour of structured control (cid:13)ow primitives such as conditionals and while-loops that replace (cid:12)xed patterns of goto's, so that rea- soning about programs becomes feasible and sometimes even elegant. For functional programs the question is which recursion schemes are to be chosen as a basis for a calculus of programs. We shall consider several recursion operators that are naturally associated with algebraic type de(cid:12)nitions. A number of general theorems are proven about these operators and subsequently used to transform programs and prove their correctness. BirdandMeertens [4, 18] haveidenti(cid:12)ed several laws for speci(cid:12)c data types (most notably(cid:12)nite lists) using which they calculated solutions to various programming problems. By embedding the calculus into a categorical framework, Bird and Meertens' work on lists can be extended to arbitrary, inductively de(cid:12)ned data types [17, 12]. Recently the group of Backhouse [1] has extended the calculus to a relational framework, thus covering indeterminancy. Independently, Paterson [21] has developed a calculus of functionalprograms similar in contents but very dissimilar in appearance (like many Australian animals) to the work referred to above. Actually if one pricks through the syntactic di(cid:11)erences the laws derived by Paterson are the same and in some cases slightly more general than those developped by the Squiggolers. This paper gives an extension of the theory to the context of lazy functional programming, i.e., forusatypeisan!-cpoandweconsideronlycontinuousfunctionsbetweentypes(categorically, we are working in the category CPO). Working in the category SET as done by for example Malcolm[17] orHagino[14] means that(cid:12)nite datatypes (de(cid:12)nedas initialalgebras)andin(cid:12)nite data types (de(cid:12)ned as (cid:12)nal co-algebras) constitute two di(cid:11)erent worlds. In that case it is not possible to de(cid:12)ne functions by induction (catamorphisms) that are applicable to both (cid:12)nite and in(cid:12)nite data types, and arbitrary recursive de(cid:12)nitions are not allowed. Working in CPO has the advantage that the carriers of initial algebras and (cid:12)nal co-algebras coincide, thus there is a single data type that comprises both (cid:12)nite and in(cid:12)nite elements. The price to be paid however is that partiality of both functions and values becomes unavoidable. 2 The data type of lists We shall illustrate the recursion patterns of interest by means of the speci(cid:12)c data type of cons- lists. So, the de(cid:12)nitions given here are actually speci(cid:12)c instances of those given in x4. Modern functional languages allow the de(cid:12)nition of cons-lists over some type A by putting: A(cid:3) ::= Nil j Cons (AkA(cid:3)) The recursive structure of this de(cid:12)nition is employed when writing functions 2 A(cid:3) ! B that destructalist; thesehavebeencalledcatamorphisms(fromthegreekpreposition(cid:20)(cid:11)(cid:28)(cid:11)meaning 2 \downwards" as in \catastrophe"). Anamorphisms are functions 2 B ! A(cid:3) (from the greek preposition (cid:11)(cid:23)(cid:11) meaning \upwards" as in \anabolism") that generate a list of type A(cid:3) from a seed from B. Functions of type A ! B whose call-tree has the shape of a cons-list are called hylomorphisms (from the Aristotelian philosophy that form and matter are one, (cid:29)(cid:21)o(cid:27) meaning \dust" or \matter"). Catamorphisms Let b 2 B and (cid:8) 2 AkB ! B, then a list-catamorphism h 2 A(cid:3) ! B is a function of the following form: h Nil = b (1) h (Cons (a;as)) = a(cid:8)(h as) In the notation of Bird&Wadler [5] one would write h = foldr b ((cid:8)). We write catamorphisms by wrapping the relevant constituents between so called banana brackets: h = (jb;(cid:8)j) (2) Countless list processing functions are readily recognizable as catamorphisms, for example length 2 A(cid:3) ! Num, or filter p 2 A(cid:3) ! A(cid:3), with p 2 A ! bool. length = (j0;(cid:8)j) where a(cid:8)n = 1+n filter p = (jNil;(cid:8)j) where a(cid:8)as = Cons (a;as); p a = as; :p a Separating the recursion pattern for catamorphisms (j j) from its ingredients b and (cid:8) makes it feasible to reason about catamorphic programs in an algebraic way. For example the Fusion Law for catamorphisms over lists reads: f (cid:14) (jb;(cid:8)j) = (jc;(cid:10)j) ( f b = c ^ f (a(cid:8)as) = a(cid:10)(f as) Without special notation pinpointing catas, such as (j j) or foldr, we would be forced to for- mulate the fusion law as follows. Let h;g be given by h Nil = b g Nil = c h (Cons (a;as)) = a(cid:8)(h as) g (Cons (a;as)) = a(cid:10)(g as) then f (cid:14) h = g if f b = c and f (a(cid:8)as) = a(cid:10)(f as). A clumsy way of stating such a simple algebraic property. 3 Anamorphisms Given a predicate p 2 B ! bool and a function g 2 B ! AkB, a list-anamorphism h 2 B ! A(cid:3) is de(cid:12)ned as: h b = Nil; p b (3) 0 = Cons (a;h b ); otherwise 0 where (a;b ) = g b Anamorphisms are not well-known in the functional programming folklore, they are called unfold by Bird&Wadler, who spend only few words on them. We denote anamorphisms by wrapping the relevant ingredients between concave lenses: h = db(g;p)ec (4) Many important list-valued functions are anamorphisms; for example zip 2 A(cid:3)kB(cid:3) ! (AkB)(cid:3) which `zips' a pair of lists into a list of pairs. zip = db(g;p)ec p (as;bs) = (as = Nil)_(bs = Nil) g (Cons (a;as);Cons (b;bs)) = ((a;b);(as;bs)) Another anamorphism is iterate f which given a, constructs the in(cid:12)nite list of iterated appli- cations of f to a. (cid:15) iterate f = db(g;false )ec where g a = (a;f a) (cid:15) We use c to denote the constant function (cid:21)x:c. Given f 2 A ! B, the map function f(cid:3) 2 A(cid:3) ! B(cid:3) applies f to every element in a given list. f(cid:3)Nil = Nil f(cid:3)(Cons (a;as)) = Cons (f a;f(cid:3)as) Since a list appears at both sides of its type, we might suspect that map can be written both as a catamorphism and as an anamorphisms. Indeed this is the case. As catamorphism: f(cid:3) = (jNil;(cid:8)j) where a (cid:8) bs = Cons (f a;bs), and as anamorphism f(cid:3) = db(g;p)ec where p as = (as = Nil) and g (Cons (a;as)) = (f a;as). Hylomorphisms A recursive function h 2 A ! C whose call-tree is isomorphic to a cons-list, i.e., a linear recursive function, is called a hylomorphism. Let c 2 C and (cid:8) 2 BkC ! C and g 2 A ! BkA 4 and p 2 A ! bool then these determine the hylomorphism h h a = c; p a (5) 0 = b(cid:8)(h a ); otherwise 0 where (b;a ) = g a This is exactly the same structure as an anamorphism except that Nil has been replaced by c and Cons by (cid:8). We write hylomorphisms by wrapping the relevant parts into envelopes. h = [[(c;(cid:8));(g;p)]] (6) A hylomorphism corresponds to the composition of an anamorphism that builds the call-tree as an explicit data structure and a catamorphism that reduces this data object into the required value. [[(c;(cid:8));(g;p)]] = (jc;(cid:8)j) (cid:14) db(g;p)ec A proof of this equality will be given in x15. An archetypical hylomorphism is the factorial function: fac = [[(1;(cid:2));(g;p)]] p n = n = 0 g (1+n) = (1+n;n) Paramorphisms Thehylomorphismde(cid:12)nitionofthefactorialmaybecorrect butisunsatisfactory fromatheoretic point of view since it is not inductively de(cid:12)ned on the data type num ::= 0 j 1+num. There is however no `simple' ' such that fac = (j'j). The problem with the factorial is that it \eats its argument and keeps it too" [27], the brute force catamorphic solution would therefore have 0 fac return a pair (n;n!) to be able to compute (n+1)!. Paramorphisms were investigated by Meertens [19] to cover this pattern of primitive recursion. For type num a paramorphism is a function h of the form: h 0 = b (7) h (1+n) = n(cid:8)(h n) For lists a paramorphism is a function h of the form: h Nil = b h (Cons (a;as)) = a(cid:8)(as;h as) 5 We write paramorphisms by wrapping the relevant constituents in barbed wire h =h[b;(cid:8)i], thus we may write fac =h[1;(cid:8)i] where n(cid:8)m = (1+n)(cid:2)m. The function tails 2 A(cid:3) ! A(cid:3)(cid:3), which gives the list of all tail segments of a given list is de(cid:12)ned by the paramorphism tails = h[Cons (Nil;Nil);(cid:8)i] where a(cid:8)(as;tls) = Cons (Cons (a;as);tls). 3 Algebraic data types In the preceding section we have given speci(cid:12)c notations for some recursion patterns in connec- tion with the particular type of cons-lists. In order to de(cid:12)ne the notions of cata-, ana-, hylo- and paramorphism for arbitrary data types, we now present a generic theory of data types and functions on them. For this we consider a recursive data type (also called `algebraic' data type 1 in Miranda) to be de(cid:12)ned as the least (cid:12)xed point of a functor . Functors A bifunctor y is a binary operation taking types into types and functions into functions such that if f 2 A ! B and g 2 C ! D then fyg 2 AyC ! ByD, and which preserves identities and composition: idyid = id fyg (cid:14) hyj = (f (cid:14) h)y(g (cid:14) j) Bifunctors are denoted by y;z;x;::: A monofunctor is a unary type operation F, which is also an operation on functions, F 2 (A ! B) ! (AF ! BF) that preserves the identity and composition. We use F;G;::: to denote monofunctors. In view of the notation A(cid:3) we write the application of a functor as a post(cid:12)x: AF. In x5 we will show that (cid:3) is a functor indeed. The data types found in all current functional languages can be de(cid:12)ned by using the following basic functors. 0 0 Product The (lazy) product DkD of two types D and D and its operation k on functions are de(cid:12)ned as: 0 0 0 0 DkD = f(d;d ) j d 2 D;d 2 D g 1 We give the de(cid:12)nitions of various concepts of category theory only for the special case of the category CPO. Also `functors' are really endo-functors, and so on. 6 0 0 (fkg) (x;x ) = (f x;g x ) Closely related to the functor k are the projection and tupling combinators: (cid:25)(cid:18) (x;y) = x (cid:25)(cid:19) (x;y) = y (f 4 g) x = (f x;g x) Using (cid:25)(cid:18);(cid:25)(cid:19) and 4 we can express fkg as fkg = (f (cid:14) (cid:25)(cid:18)) 4 (g (cid:14) (cid:25)(cid:19)). We can also de(cid:12)ne 4 using k and the doubling combinator (cid:1) x = (x;x), since f 4 g = fkg (cid:14) (cid:1). 0 0 Sum The sum D j D of D and D and the operation j on functions are de(cid:12)ned as: 0 0 D j D = (f0gkD)[(f1gkD )[f?g (f j g) ? = ? (f j g) (0;x) = (0;f x) 0 0 (f j g) (1;x ) = (1;g x ) The arbitrarily chosen numbers 0 and 1 are used to `tag' the values of the two summands so that they can be distinguished. Closely related to the functor j are the injection and selection combinators: (cid:18){ x = (0;x) (cid:19){ y = (1;y) (f 5 g) ? = ? (f 5 g) (0;x) = f x (f 5 g) (1;y) = g y with which we can write f j g = ((cid:18){ (cid:14) f) 5 ((cid:19){ (cid:14) g). Using r which removes the tags from its argument, r ? = ? and r (i;x) = x, we can de(cid:12)ne f 5 g = r (cid:14) f j g. 0 Arrow The operation ! that forms the function space D ! D of continuous functions from 0 D to D , has as action on functions the `wrapping' function: (f ! g) h = g (cid:14) h (cid:14) f Often we will use the alternative notation (g f) h = g (cid:14) h (cid:14) f, where we have swapped the arrow already so that upon application the arguments need not be moved, thus localizing the F changes occurring during calculations. The functional (f g) h = f (cid:14) hF (cid:14) g wraps its F-ed argument between f and g. 7 Closely related to the ! are the combinators: curry f x y = f (x;y) uncurry f (x;y) = f x y eval (f;x) = f x Note that ! is contra-variant in its (cid:12)rst argument, i.e. (f ! g) (cid:14) (h ! j) = (h (cid:14) f) ! (g (cid:14) j). Identity, Constants The identity functor I is de(cid:12)ned on types as DI = D and on functions as fI = f. Any type D induces a functor with the same name D, whose operation on objects is given by CD = D, and on functions fD = id. Lifting For mono-functors F;G and bi-functor y we de(cid:12)ne the mono-functors FG and FyG by x(FG) = (xF)G x(FyG) = (xF)y(xG) for both types and functions x. In view of the (cid:12)rst equation we need not write parenthesis in xFG. Notice that in (FyG) the bi-functor y is `lifted' to act on functors rather than on objects; (FyG) is itself a mono-functor. Sectioning Analogous to the sectioning of binary operators, (a(cid:8)) b = a(cid:8)b and ((cid:8)b) a = a(cid:8)b we de(cid:12)ne sectioning of bi-functors y; (Ay) = AyI (fy) = fyid hence B(Ay) = AyB and f(Ay) = idyf. Similarly we can de(cid:12)ne sectioning of y in its second argument, i.e. (yB) and (yf). It is not too di(cid:14)cult to verify the following two properties of sectioned functors: (fy) (cid:14) g(Ay) = g(By) (cid:14) (fy) for all f 2 A ! B (8) (fy) (cid:14) (gy) = ((f (cid:14) g)y) (9) Taking fyg = g ! f, thus (fy) = (f(cid:14)) gives some nice laws for function composition. 8 Laws for the basic combinators There are various equations involving the above combinators, we state nothing but a few of these. In parsing an expression function composition has least binding power while k binds stronger than j. (cid:25)(cid:18) (cid:14) fkg = f (cid:14) (cid:25)(cid:18) f j g (cid:14)(cid:18){ = (cid:18){ (cid:14) f (cid:25)(cid:18) (cid:14) f 4 g = f f 5 g (cid:14)(cid:18){ = f (cid:25)(cid:19) (cid:14) fkg = g (cid:14) (cid:25)(cid:19) f j g (cid:14)(cid:19){ = (cid:19){ (cid:14) g (cid:25)(cid:19) (cid:14) f 4 g = g f 5 g (cid:14)(cid:19){ = g ((cid:25)(cid:18) (cid:14) h) 4 ((cid:25)(cid:19) (cid:14) h) = h (h (cid:14)(cid:18){) 5 (h (cid:14)(cid:19){) = h ( h strict (cid:25)(cid:18) 4 (cid:25)(cid:19) = id (cid:18){ 5(cid:19){ = id fkg (cid:14) h 4 j = (f (cid:14) h) 4 (g (cid:14) j) f 5 g (cid:14) h j j = (f (cid:14) h) 5 (g (cid:14) j) f 4 g (cid:14) h = (f (cid:14) h) 4 (g (cid:14) h) f (cid:14) g 5 h = (f (cid:14) g) 5 (f (cid:14) h) ( f strict fkg = hkj (cid:17) f = h^g = j f j g = h j j (cid:17) f = h^g = j f 4 g = h 4 j (cid:17) f = h^g = j f 5 g = h 5 j (cid:17) f = h^g = j A nice law relating 4 and 5 is the abides law: (f 4 g) 5 (h 4 j) = (f 5 h) 4 (g 5 j) (10) Varia The one element type is denoted 1 and can be used to model constants of type A by nullary functions of type 1 ! A. The only member of 1 called void is denoted by (). In some examples we use for a given predicate p 2 A ! bool, the function: p? 2 A ! A j A p? a = ?; p a = ? = (cid:18){ a; p a = true = (cid:19){ a; p a = false thus f 5 g (cid:14) p? models the familiar conditional if p then f else g (cid:12). The function VOID maps its argument to void: VOID x = (). Some laws that hold for these functions are: VOID(cid:14) f = VOID p? (cid:14) x = x j x (cid:14) (p (cid:14) x)? In order to make recursion explicit, we use the operator (cid:22) 2 (A ! A) ! A de(cid:12)ned as: (cid:22) f = x where x = f x 9 We assume that recursion (like x = f x) is well de(cid:12)ned in the meta-language. Let F;G be functors and 'A 2 AF ! AG for any type A. Such a ' is called a polymorphic function. A natural transformation is a family of functions 'A (omitting subscripts whenever possible) such that: 8f : f 2 A ! B : 'B (cid:14) fF = fG (cid:14) 'A (11) (cid:1) As a convenient shorthand for (11) we use ' 2 F ! G to denote that ' is a natural trans- formation. The \Theorems For Free!" theorem of Wadler, deBruin and Reynolds [28, 9, 22] states that any function de(cid:12)nable in the polymorphic (cid:21)-calculus is a natural transformation. If ' is de(cid:12)ned using (cid:22), one can only conclude that (11) holds for strict f. Recursive types After all this stu(cid:11) on functors we have (cid:12)nally armed ourselves su(cid:14)ciently to abstract from the peculiarities of cons-lists, and formalize recursively de(cid:12)ned data types in general. Let F be a monofunctor whose operation of functions is continuous, i.e., all monofunctors de(cid:12)ned using the above basic functors or any of the map-functors introduced in x5. Then there exists a type L and two strict functions inF 2 LF ! L and outF 2 L ! LF (omitting F subscripts whenever possible) which are each others inverse and even id = (cid:22)(in out) [6, 23, 16, 24, 30, 12]. We let (cid:22)F denote the pair (L;in) and say that it is \the least (cid:12)xed point of F". Since in and out are each others inverses we have that LF is isomorphic to L, and indeed L is | upto isomorphism | a (cid:12)xed point of F. For example taking XL = 1 j AkX, we have that (A(cid:3);in) = (cid:22)L de(cid:12)nes the data type of cons- lists over A for any type A. If we put Nil = in (cid:14)(cid:18){ 2 1 ! A(cid:3) and Cons = in (cid:14)(cid:19){ 2 AkA(cid:3) ! A(cid:3), we get the more familiar (A(cid:3);Nil 5 Cons) = (cid:22)L. Another example of data types, binary trees with leaves of type A results from taking the least (cid:12)xed point of XT = 1 j A j XkX. Backward lists with elements of type A, or snoc lists as they are sometimes called, are the least (cid:12)xed point of XL = 1 j XkA. Natural numbers are speci(cid:12)ed as the least (cid:12)xed point of XN = 1 j X. 4 Recursion Schemes Now that we have given a generic way of de(cid:12)ning recursive data types, we can de(cid:12)ne cata-, ana-, hylo- and paramorphisms over arbitrary data types. Let (L;in) = (cid:22)F, ' 2 AF ! A; 2 10