How to Keep Your Neighbours in Order
Total Page:16
File Type:pdf, Size:1020Kb
How to Keep Your Neighbours in Order Conor McBride University of Strathclyde [email protected] Abstract 2. How to Hide the Truth I present a datatype-generic treatment of recursive container types If we intend to enforce invariants, we shall need to mix a little whose elements are guaranteed to be stored in increasing order, bit of logic in with our types and a little bit of proof in with our with the ordering invariant rolled out systematically. Intervals, lists programming. It is worth taking some trouble to set up our logical and binary search trees are instances of the generic treatment. On apparatus to maximize the effort we can get from the computer the journey to this treatment, I report a variety of failed experiments and to minimize the textual cost of proofs. We should prefer to and the transferable learning experiences they triggered. I demon- encounter logic only when it is dangerously absent! strate that a total element ordering is enough to deliver insertion and Our basic tools are the types representing falsity and truth by flattening algorithms, and show that (with care about the formula- virtue of their number of inhabitants: tion of the types) the implementations remain as usual. Agda’s in- data 0 : Set where stance arguments and pattern synonyms maximize the proof search -- no constructors! done by the typechecker and minimize the appearance of proofs record 1 : Set where constructor hi -- no fields! in program text, often eradicating them entirely. Generalizing to Dependent types allow us to compute sets from data. E.g., we indexed recursive container types, invariants such as size and bal- can represent evidence for the truth of some Boolean expression ance can be expressed in addition to ordering. By way of example, I which we might have tested. implement insertion and deletion for 2-3 trees, ensuring both order and balance by the discipline of type checking. data 2 : Set where tt ff : 2 So : 2 ! Set 1. Introduction So tt = 1 So ff = 0 It has taken years to see what was under my nose. I have been experimenting with ordered container structures for a long time A set P which evaluates to 0 or to 1 might be considered [McBride(2000)]: how to keep lists ordered, how to keep binary ‘propositional’ in that we are unlikely to want to distinguish its search trees ordered, how to flatten the latter to the former. Re- inhabitants. We might even prefer not even to see its inhabitants. cently, the pattern common to the structures and methods I had of- I define a wrapper type for propositions whose purpose is to hide ten found effective became clear to me. Let me tell you about it. proofs. Patterns are, of course, underarticulated abstractions. Correspond- record (P : Set): Set where ingly, let us construct a universe of container-like datatypes ensur- pq constructor ! ing that elements are in increasing order, good for intervals, ordered lists, binary search trees, and more besides. field ffprf gg : P This paper is a literate Agda program. The entire development Agda uses braces to indicate that an argument or field is to be is available at https://github.com/pigworker/Pivotal. As suppressed by default in program texts and inferred somehow by well as making the headline contributions the typechecker. Single-braced variables are solved by unification, • a datatype-generic treatment of ordering invariants and opera- in the tradition of Milner. Doubled braces indicate instance argu- tions which respect them ments, inferred by contextual search: if just one hypothesis can take the place of an instance argument, it is silently filled in, allowing us • a technique for hiding proofs from program texts a tiny bit of proof automation [Devriese and Piessens(2011)]. If an • a precise implementation of insertion and deletion for 2-3 trees inhabitant of pSo bq is required, we may write ! to indicate that we expect the truth of b to be known. I take the time to explore the design space, reporting a selection of Careful positioning of instance arguments seeds the context the wrong turnings and false dawns I encountered on my journey to with useful information. We may hypothesize over them quietly, these results. I try to extrapolate transferable design principles, so and support forward reasoning with a ‘therefore’ operator. that others in future may suffer less than I. ) : Set ! Set ! Set P ) T = ffp : P gg ! T infixr 3 ) Permission to make digital or hard copies of all or part of this work for personal or ) : 8fPT g ! pPq ! (P ) T ) ! T classroom use is granted without fee provided that copies are not made or distributed ! t = t for profit or commercial advantage and that copies bear this notice and the full citation ) on the first page. To copy otherwise, to republish, to post on servers or to redistribute This apparatus can give the traditional conditional a subtly more to lists, requires prior specific permission and/or a fee. informative type, thus: Copyright c ACM [to be supplied]. $5.00 : : 2 ! 2; : tt = ff; : ff = tt if then else : ?i : 8fX g ! 2 ! Maybe X ! Maybe X 8fX g b ! (So b ) X ) ! (So (: b) ) X ) ! X b ?i mx = if b then mx else no if tt then t else f = t infixr 4 ?i if ff then t else f = f ?i infix 1 if then else The guarding operator allows us to attach a Boolean test. We may now validate the range of a Tree. If ever there is a proof of 0 in the context, we should be able to ask for anything we want. Let us define valid : Tree ! Maybe STRange valid leaf = yes ; magic : fX : Setg ! 0 ) X valid (node lpr ) with valid l j valid r magic ff()gg ::: j yes ;j yes ; = yes (p −p) using Agda’s absurd pattern to mark the impossible instance ::: j yes ;j yes (c −d) = lepc ?i yes (p −d) argument which shows that no value need be returned. E.g., ::: j yes (a −b) j yes ; = lebp ?i yes (a −p) if tt then ff else magic : 2. ::: j yes (a −b) j yes (c −d) Instance arguments are not a perfect fit for proof search: they = lebp ?i lepc ?i yes (a −d) were intended as a cheap alternative to type classes, hence the ::: j j = no requirement for exactly one candidate instance. For proofs we might prefer to be less fussy about redundancy, but we shall manage As valid is a fold over the structure of Tree, we can follow my perfectly well for the purposes of this paper. colleagues Bob Atkey, Neil Ghani and Patricia Johann in comput- ing the partial refinement [Atkey et al.(2012)Atkey, Johann, and Ghani] 3. Barking Up the Wrong Search Trees of Tree which valid induces. We seek a type BST : STRange ! Set such that BST r =∼ ft : Tree j valid t = yes rg and we find David Turner [Turner(1987)] notes that whilst quicksort is often it by refining the type of each constructor of Tree with the check cited as a program which defies structural recursion, it performs the performed by the corresponding case of valid, assuming that the same sorting algorithm (although not with the same memory usage subtrees yielded valid ranges. We can calculate the conditions to pattern) as building a binary search tree and then flattening it. The check and the means to compute the output range if successful. irony is completed by noting that the latter sorting algorithm is the archetype of structural recursion in Rod Burstall’s development of lOK : STRange ! P ! 2 the concept [Burstall(1969)]. Binary search trees have empty leaves lOK ; p = tt and nodes labelled with elements which act like pivots in quicksort: lOK ( −u) p = leup the left subtree stores elements which precede the pivot, the right rOK : P ! STRange ! 2 subtree elements which follow it. Surely this invariant is crying out rOK p ; = tt to be a dependent type! Let us search for a type for search trees. rOK p (l − ) = lepl We could, of course, define binary search trees as ordinary node- labelled trees with parameter P giving the type of pivots: rOut : STRange ! P ! STRange ! STRange rOut ; p ; = p −p data Tree : Set where rOut ; p ( −u) = p −u leaf : Tree; node : Tree ! P ! Tree ! Tree rOut (l − ) p ; = l −p We might then define the invariant as a predicate IsBST : Tree ! rOut (l − ) ( −u) = l −u Set, implement insertion in our usual way, and prove separately We thus obtain the following refinement from Tree to BST: that our program maintains the invariant. However, the joy of de- pendently typed programming is that refining the types of the data data BST : STRange ! Set where themselves can often alleviate or obviate the burden of proof. Let leaf : BST ; us try to bake the invariant in. node : 8flr g ! BST l ! (p : P) ! BST r ! What should the type of a subtree tell us? If we want to check So (lOK lp ) ) So (rOK pr ) ) BST (rOut lpr ) the invariant at a given node, we shall need some information about the subtrees which we might expect comes from their type. We Attempting to implement insertion. Now that each binary search require that the elements left of the pivot precede it, so we could tree tells us its type, can we implement insertion? Rod Burstall’s require the whole set of those elements represented somehow, but implementation is as follows of course, for any order worthy of the name, it suffices to check only the largest.