Investigating Minimally Strict Functions in Functional Programming
Total Page:16
File Type:pdf, Size:1020Kb
Investigating Minimally Strict Functions in Functional Programming Dissertation zur Erlangung des akademischen Grades Doktor-Ingenieur (Dr.-Ing.) der Technischen Fakultät der Christian-Albrechts-Universität zu Kiel Jan Christiansen Kiel 2011 1. Gutachter Prof. Dr. Rudolf Berghammer 2. Gutachter Priv.-Doz. Dr. Frank Huch Datum der mündlichen Prüfung 12. Januar 2012 ii Contents 1 Introduction1 2 Preliminaries9 2.1 Introduction to Haskell............................9 2.1.1 Algebraic Data Types.........................9 2.1.2 Functions............................... 11 2.1.3 Types.................................. 15 2.1.4 Parametric Polymorphism...................... 16 2.1.5 Higher-Order............................. 18 2.1.6 Type Classes.............................. 21 2.2 Denotation of a Simple Functional Language............... 24 3 Non-Strict Evaluation 33 3.1 Advantages of Non-Strict Evaluation.................... 34 3.2 Unnecessarily Strict Functions........................ 36 4 Mathematical Model of Minimally Strict Functions 43 4.1 Least Strict Functions............................. 43 4.2 Sequential and Demanded Positions.................... 49 4.3 Minimally Strict Functions.......................... 65 4.3.1 Sufficiency of the Criterion..................... 69 4.3.2 Necessity of the Criterion...................... 75 5 Implementation of Sloth 83 5.1 Enumerating Test Cases........................... 84 5.2 Checking Test Cases............................. 90 5.3 Presenting Counter-Examples........................ 95 5.4 Identifying Sequential Positions....................... 97 6 Minimally Strict Polymorphic Functions 101 6.1 Introduction.................................. 102 6.2 Free Theorems................................. 104 6.3 Less Strict Functions on Lists........................ 108 6.4 Less Strict Functions in the Presence of seq................ 114 6.5 Minimally Strict Functions on Lists..................... 122 6.6 Generalization................................. 123 iii Contents 7 Case Studies 127 7.1 Deriving a Less Strict Implementation................... 127 7.2 Peano Multiplication............................. 133 7.3 Binary Arithmetics.............................. 136 7.4 The split Package............................... 144 7.4.1 The Function splitWhen....................... 146 7.4.2 The Function insertBlanks...................... 153 7.5 Reversing Lists................................ 157 8 Conclusion 165 8.1 Summary.................................... 165 8.2 Future Work.................................. 168 8.2.1 Functional Programming...................... 168 8.2.2 Functional-Logic Programming................... 171 A Proofs from Chapter4 173 A.1 Proofs from Section 4.2............................ 173 A.2 Proofs from Section 4.3.1........................... 175 A.3 Proofs from Section 4.3.2........................... 179 B Proofs from Chapter6 187 B.1 Proofs from Section 6.3............................ 187 iv Acknowledgments First of all I am very grateful to Olaf Chitil for introducing me to his excellent idea of checking whether a function is unnecessarily strict. Furthermore, I want to thank Rudolf Berghammer for his support, for tolerating my occasional loose tongue, and for not loosing patience with me. I am indebted to Fabian Reck for being an excellent officemate and a wine expert, and for always lending me an ear. Besides, I want to thank Daniel Seidel for being a great workmate and host and for even becoming a good friend. I owe thank to Janis Voigtländer for introducing me to the idea of free theorems and for collaborating with me in general. With respect to the work at hand I want to thank Ole Cordsen, Nikita Danilenko, Sebastian Fischer, Hauke Fuhrmann, Frank Kupke, Björn Peemöller, Matthias Quednau, Fabian Reck, Daniel Seidel, and Gabriel Wicke for reading parts of this thesis. I want to thank the “gang from the seventh floor”, which, besides some members already mentioned, includes Bernd Braßel, Stefan Bolus, Michael Hanus, Thomas Heß, Frank Huch, Ina Pfannschmidt, Peter Pichol, and Ulrike Pollakowski for providing a nice working environment and sharing lunch. In particular, I would like to single out the benefits that we all enjoy by having a great secretary like Ulrike. Moreover, thanks to the countless number of students for giving me the opportunity to teach them the basic parts of functional programming in several first term practical courses. Also, I would like to thank the easily countable number of students that took my advanced courses. Finally, a very big thanks to the team of the cafetaria of the “Studentenwerk Schleswig-Holstein” without whose constant supply with coffee and sugar in the form of muffins and donuts this work could not have been finished. v 1 Introduction In this thesis we consider the non-strict, functional programming language Haskell (Peyton Jones 2003). First of all, functional programing languages belong to the class of declarative programming languages. While in an imperative programming language the programmer specifies a procedure to get a solution to a problem, in a declarative language the programmer instead specifies the structure of a solution. In other words, while in an imperative language we define how a solution is computed, in a declarative language we rather define what is computed. Besides, Haskell has a quite strong type system, which is able to catch some bugs at compile-time, while these bugs do not emerge until run-time in a language with a weaker type system or no type system at all. And, finally, features like higher-order functions and overload- ing allow programmers to reuse code, which results in programs that are smaller and easier to maintain. However, even with a comparatively high-level language like Haskell, program- mers may write programs that are not optimal with respect to a certain criterion. Obviously, the most important criterion is correctness. That is, we prefer a pro- gram that meets our specifications over a program that does not. There are several approaches that assist programmers in writing correct Haskell programs like prop- erty-based testing (Claessen and Hughes 2000) or even automatic proving (Sonnex et al. 2011). Nonetheless, there are other important criteria to rate programs. For example, we could classify several implementations of the same algorithm in one language by means of run-time or memory consumption. This thesis investigates another criterion, namely, rating Haskell programs by means of strictness. Intuitively, in contrast to a strict programming language, in a non-strict program- ming language like Haskell a function only inspects the part of its argument that is needed to yield a certain part of its result. In the extreme case of a constant function, for example, the function does not evaluate its argument at all as the result of the function does not depend on the argument. Nevertheless, we can still define a con- stant function in Haskell that unnecessarily inspects its argument. More generally speaking, in Haskell a function definition rather non-declaratively specifies how an argument is inspected. There are often several implementations of functions with the same basic behavior that inspect different parts of their argument. While in the case of a constant function it is quite easy to observe that it does not have to inspect its argument, we will see that in general it is more difficult to observe whether a function can yield the same result by inspecting a smaller part of its argument. To rate functions with respect to strictness, we consider the following definition. A function f is less strict than a function g if the function f never inspects a larger part of its argument than the function g does, to yield a certain result. In addition, there exists a result such that f inspects a strictly smaller part of its argument than g does. We call a function f with these properties less strict than g. This “definition” is quite informal, and we will later give a formal definition of the less-strict relation by 1 1 Introduction means of a denotational semantics. For now this intuitive view should be sufficient for this introduction and will be helpful later on. As an example for the interest in less strict functions, let us consider the standard Haskell function partition. Waldmann(2000) as well as Russell(2000) have stated that the implementation of this function was more strict than necessary. The function partition takes a predicate — a unary funtion that yields a Boolean value — and a list — an ordered collection of values of equal type — and yields a pair — a container for two values. The pair holds two lists where the first list contains the elements that satisfy the predicate and the second list contains the elements that do not. It has been observed that partition, at that time, was unnecessarily strict because the implementation did not comply with an abstract specification given in the Haskell language report (Peyton Jones 2003). The language report specifies the behavior of partition by means of the standard Haskell function filter. The function filter takes a list and a predicate and yields a list of all elements that satisfy the predicate. The behavior of partition is specified by the following equivalence where p is an arbitrary predicate and xs an arbitrary list. Furthermore, the symbol ≡ denotes that the left-hand and the right-hand side yield the same results when evaluated. partition p xs ≡ (filter p xs, filter (not ◦ p) xs) The application of a function is denoted by juxta-position, for example,