Deriving monadic quicksort (Declarative Pearl)
aa r X i v : . [ c s . P L ] J a n Declarative Pearl: Deriving Monadic Quicksort ⋆ Shin-Cheng Mu and Tsung-Ju Chiang Academia Sinica, Taiwan National Taiwan University, Taiwan
Abstract.
To demonstrate derivation of monadic programs, we presenta specification of sorting using the non-determinism monad, and derivepure quicksort on lists and state-monadic quicksort on arrays. In thederivation one may switch between point-free and pointwise styles, anddeploy techniques familiar to functional programmers such as patternmatching and induction on structures or on sizes. Derivation of statefulprograms resembles reasoning backwards from the postcondition.
Keywords: monads · program derivation · equational reasoning · non-determinism · state · quicksort This pearl presents two derivations of quicksort. The purpose is to demonstratereasoning and derivation of monadic programs. In the first derivation we presenta specification of sorting using the non-determinism monad, from which we derivea pure function that sorts a list. In the second derivation we derive an imperativealgorithm, expressed in terms of the state monad, that sorts an array.Before we dive into the derivations, we shall explain our motivation. Programderivation is the technique of formally constructing a program from a problemspecification. In functional derivation, the specification is a function that ob-viously matches the problem description, albeit inefficiently. It is then stepwisetransformed to a program that is efficient enough, where every step is justified bymathematical properties guaranteeing that the program equals the specification,that is, for all inputs they compute exactly the same output.It often happens, for certain problem, that several answers are equally pre-ferred. In sorting, for example, the array to be sorted might contain items withidentical keys. It would be inflexible, if not impossible, to decide in the specifi-cation how to resolve the tie: it is hard to predict how quicksort arranges itemswith identical keys before actually deriving quicksort. Such problems are better ⋆ This is a post-peer-review, pre-copyedit version of an article published inNakano K., Sagonas K. (eds) Functional and Logic Programming (FLOPS2020), Springer. The final authenticated version is available online at:https://doi.org/10.1007/978-3-030-59025-3 8. Unless we confine ourselves to stable sorting. Shin-Cheng Mu and Tsung-Ju Chiang modelled as non-deterministic mappings from the input to all valid outputs. Thederived program no longer equals but refines the specification. To cope with non-determinism, there was a trend in the 90’s generalisingfrom functions to relations [1,4]. Although these relational calculi are, for ad-vocates including the authors of this paper, concise and elegant, for those whowere not following this line of development, these calculi are hard to compre-hend and use. People, in their first and often only exposure to the calculi, oftencomplained that the notations are too bizarre, and reasoning with inequality (re-finement) too complex. One source of difficulties is that notations of relationalcalculus are usually point-free — that is, about composing relations instead ofapplying relations to arguments. There have been attempts (e.g [13,5]) design-ing pointwise notations, which functional programmers are more familiar with.Proposals along this line tend to exhibit confusion when functions are appliedto non-deterministic values — β -reduction and η -conversion do not hold. Oneexample [13] is that ( λ x → x − x ) (0 ) denotes non-deterministicchoice, always yields 0, while (0 − (0
1) could be 0, 1, or − A monad consists of a type constructor m :: ∗ → ∗ paired with two operators,can be modelled in Haskell as a type class: class Monad m where {·} :: a → m a ( >> =) :: m a → ( a → m b ) → m b . This is standard in imperative program derivation — Dijkstra [6] argued that weshould take non-determinism as default and determinism as a special case. https://scm.iis.sinica.edu.tw/home/2020/deriving-monadic-quicksort/eclarative Pearl: Deriving Monadic Quicksort 3 The operator {·} is usually called return or unit . Since it is used pervasivelyin this pearl, we use a shorter notation for brevity. One can either think of itas mimicking the notation for a singleton set, or C-style syntax for a block ofeffectful program. They should satisfy the following monad laws : m >> = {·} = m , { x } >> = f = f x ,( m >> = f ) >> = g = m >> = ( λ x → f x >> = g ) .A standard operator ( >> ) :: Monad m ⇒ m a → m b → m b , defined by m >> m = m >> = λ → m , is handy when we do not need the result of m . Monadic functions can be combined by Kleisli composition ( > = > ), defined by f > = > g = λ x → f x >> = g .Monads usually come with additional operators corresponding to the effectsthey provide. Regarding non-determinism, we assume two operators ∅ and ( ),respectively denoting failure and non-deterministic choice: class Monad m ⇒ MonadPlus m where ∅ :: m a ( ) :: m a → m a → m a . It might be a good time to note that this pearl uses type classes for two purposes:firstly, to be explicit about the effects a program uses. Secondly, the notationimplies that it does not matter which actual implementation we use for m ,as long as it satisfies all the properties we demand — as Gibbons and Hinze[7] proposed, we use the properties, not the implementations, when reasoningabout programs. The style of reasoning in this pearl is not tied to type classesor Haskell, and we do not strictly follow the particularities of type classes in thecurrent Haskell standard. It is usually assumed that ( ) is associative with ∅ as its identity: ∅ m = m = m ∅ , ( m m ) m = m ( m m ) .For the purpose of this pearl, we also demand that ( ) be idempotent and com-mutative. That is, m m = m and m n = n m . Efficient implementations ofsuch monads have been proposed (e.g. [10]). However, we use non-determinismmonad only in specification. The derived programs are always deterministic.The laws below concern interaction between non-determinism and ( >> =): ∅ >> = f = ∅ , (1) m >> ∅ = ∅ , (2)( m m ) >> = f = ( m >> = f ) ( m >> = f ) , (3) m >> = ( λ x → f x f x ) = ( m >> = f ) ( m >> = f ) . (4) For example, we overlook that a
Monad must also be
Applicative , MonadPlus be Alternative , and that functional dependency is needed in a number of places. Shin-Cheng Mu and Tsung-Ju Chiang
Left-zero (1) and left-distributivity (3) are standard — the latter says that ( )is algebraic. When mixed with state, right-zero (2) and right-distributivity (4)imply that each non-deterministic branch has its own copy of the state [14]. We are now ready to present a monadic specification of sorting. Bird [3] demon-strated how to derive various sorting algorithms from relational specifications. InSection 4 and 5 we show how quicksort can be derived in our monadic calculus.We assume a type
Elm (for “elements”) associated with a total preorder ( ).To sort a list xs :: List Elm is to choose, among all permutation of xs , those thatare sorted: slowsort :: MonadPlus m ⇒ List Elm → m ( List Elm ) slowsort = perm > = > filt sorted , where perm :: MonadPlus m ⇒ List a → m ( List a ) non-deterministically com-putes a permutation of its input, sorted :: List Elm → Bool checks whether a listis sorted, and filt p x returns x if p x holds, and fails otherwise: filt :: MonadPlus m ⇒ ( a → Bool ) → a → m afilt p x = guard ( p x ) >> { x } . The function guard b = if b then {} else ∅ is standard. The predicate sorted :: List Elm → Bool can be defined by: sorted [ ] =
True sorted ( x : xs ) = all ( x ) xs ∧ sorted xs . The following property can be proved by a routine induction on ys : sorted ( ys ++ [ x ] ++ zs ) ≡ sorted ys ∧ sorted zs ∧ all ( x ) ys ∧ all ( x ) zs . (5)Now we consider the permutation phase. As shown by Bird [3], what sortingalgorithm we end up deriving is often driven by how the permutation phase isperformed. The following definition of perm , for example: perm [ ] = { [ ] } perm ( x : xs ) = perm xs >> = insert x , where insert x xs non-deterministically inserts x into xs , would lead us to inser-tion sort. To derive quicksort, we use an alternative definition of perm : perm :: MonadPlus m ⇒ List a → m ( List a ) perm [ ] = { [ ] } perm ( x : xs ) = split xs >> = λ ( ys , zs ) → liftM2 (++[ x ]++) ( perm ys ) ( perm zs ) . eclarative Pearl: Deriving Monadic Quicksort 5 where liftM2 ( ⊕ ) m m = m >> = λ x → m >> = λ x → { x ⊕ x } , and split non-deterministically splits a list. When the input has more than one element,we split the tail into two, permute them separately, and insert the head in themiddle. The monadic function split is given by: split :: MonadPlus m ⇒ List a → m ( List a × List a ) split [ ] = { ([ ] , [ ]) } split ( x : xs ) = split xs >> = λ ( ys , zs ) → { ( x : ys , zs ) } { ( ys , x : zs ) } . This completes the specification. One may argue that the second definition of perm is not one that, as stated in Section 1, “obviously” implied by the problemdescription. Bird [3] derived the second one from the first in a relational setting,and we can also show that the two definitions are equivalent.
In this section we derive a divide-and-conquer property of slowsort . It allows usto refine slowsort to the well-known recursive definition of quicksort on lists, andis also used in the next section to construct quicksort on arrays.
Refinement
We will need to first define our concept of program refinement.We abuse notations from set theory and define: m ⊆ m ≡ m m = m . The righthand side m m = m says that every result of m is a possibleresult of m . When m ⊆ m , we say that m refines m , m can be refined tom , or that m subsumes m . Note that this definition applies not only to thenon-determinism monad, but to monads having other effects as well. We denote( ⊆ ) lifted to functions by ( ˙ ⊆ ): f ˙ ⊆ g = ( ∀ x : f x ⊆ g x ) . That is, f refines g if f x refines g x for all x .When we use this notation, f and g are always functions returning monads, which is sufficient for this pearl.One can show that the definition of ( ⊆ ) is equivalent to m ⊆ m ≡ ( ∃ n : m n = m ), and that ( ⊆ ) and ( ˙ ⊆ ) are both reflexive, transitive, and anti-symmetric ( m ⊆ n ∧ n ⊆ m ≡ n = m ). Furthermore, ( >> =) respects refinement: Lemma 1.
Bind ( >> =) is monotonic with respect to ( ⊆ ) . That is, m ⊆ m ⇒ m >> = f ⊆ m >> = f , and f ˙ ⊆ f ⇒ m >> = f ⊆ m >> = f . Having Lemma 1 allows us to refine programs in a compositional manner. Theproof of Lemma 1 makes use of (3) and (4).
Shin-Cheng Mu and Tsung-Ju Chiang
Commutativity and guard
We say that m and n commute if m >> = λ x → n >> = λ y → f x y = n >> = λ y → m >> = λ x → f x y . It can be proved that guard p commutes with all m if non-determinism is theonly effect in m — a property we will need many times. Furthermore, havingright-zero (2) and right-distributivity (4), in addition to other laws, one canprove that non-determinism commutes with other effects. In particular, non-determinism commutes with state.We mention two more properties about guard : guard ( p ∧ q ) can be splitinto two, and guard s with complementary predicates can be refined to if : guard ( p ∧ q ) = guard p >> guard q , (6)( guard p >> m ) ( guard ( ¬ · p ) >> m ) ⊇ if p then m else m . (7) Divide-and-Conquer
Back to slowsort . We proceed with usual routine in func-tional programming: case-analysis on the input. For the base case, slowsort [ ] = { [ ] } . For the inductive case, the crucial step is the commutativity of guard : slowsort ( p : xs )= { expanding definitions, monad laws } split xs >> = λ ( ys , zs ) → perm ys >> = λ ys ′ → perm zs >> = λ zs ′ → filt sorted ( ys ′ ++ [ p ] ++ zs ′ )= { by (5) } split xs >> = λ ( ys , zs ) → perm ys >> = λ ys ′ → perm zs >> = λ zs ′ → guard ( sorted ys ′ ∧ sorted zs ′ ∧ all ( p ) ys ′ ∧ all ( p ) zs ′ ) >> { ys ′ ++ [ p ] ++ zs ′ } = { (6) and that guard commutes with non-determinism } split xs >> = λ ( ys , zs ) → guard ( all ( p ) ys ∧ all ( p ) zs ′ ) >> ( perm ys >> = filt sorted ) >> = λ ys ′ → ( perm zs >> = filt sorted ) >> = λ zs ′ →{ ys ′ ++ [ p ] ++ zs ′ } . Provided that we can construct a function partition such that { partition p xs } ⊆ split xs >> = filt ( λ ( ys , zs ) → all ( p ) ys ∧ all ( p ) zs ) , we have established the following divide-and-conquer property: slowsort ( p : xs ) ⊇ { partition p xs } >> = λ ( ys , zs ) → slowsort ys >> = λ ys ′ → slowsort zs >> = λ zs ′ →{ ys ′ ++ [ p ] ++ zs ′ } . (8)The derivation of partition proceeds by induction on the input. In the casefor xs := x : xs we need to refine two guarded choices, ( guard ( x p ) >> { x : eclarative Pearl: Deriving Monadic Quicksort 7 ys , zs } ) ( guard ( p x ) >> { ys , x : zs } ), to an if branching. When x and p equal,the specification allows us to place x in either partition. For no particular reason,we choose the left partition. That gives us: partition p [ ] = ([ ] , [ ]) partition p ( x : xs ) = let ( ys , zs ) = partition p xs in if x p then ( x : ys , zs ) else ( ys , x : zs ) . Having partition derived, it takes only a routine induction on the length of inputlists to show that {·} · qsort ˙ ⊆ slowsort , where qsort is given by: qsort [ ] = [ ] qsort ( p : xs ) = let ( ys , zs ) = partition p xs in qsort ys ++ [ p ] ++ qsort zs . As is typical in program derivation, the termination of derived program is shownseparately afterwards. In this case, qsort terminates because the input list de-creases in size in every recursive call — for that we need to show that, in thecall to partition , the sum of lengths of ys and zs equals that of xs . One of the advantages of using a monadic calculus is that we can integrate effectsother than non-determinism into the program we derive. In this section we derivean imperative quicksort on arrays, based on previously established properties.
We assume that our state is an
Int -indexed, unbounded array containing elementsof type e , with two operations that, given an index, respectively read from andwrite to the array: class Monad m ⇒ MonadArr e m where read :: Int → m ewrite :: Int → e → m () . They are assumed to satisfy the following laws: read-write: read i >> = write i = { () } , write-read: write i x >> read i = write i x >> { x } , write-write: write i x >> write i x ′ = write i x ′ , read-read: read i >> = λ x → read i >> = λ x ′ → f x x ′ = read i >> = λ x → f x x .Furthermore, we assume that (1) read i and read j commute; (2) write i x and write j y commute if i = = j ; (3) write i x and read j commute if i = = j . Shin-Cheng Mu and Tsung-Ju Chiang readList :: MonadArr e m ⇒ Int → Nat → m ( List e ) readList i { [ ] } readList i (1 + k ) = liftM2 (:) ( read i ) ( readList ( i + 1) k ) , writeList :: MonadArr e m ⇒ Int → List e → m () writeList i [ ] = { () } writeList i ( x : xs ) = write i x >> writeList ( i + 1) xs , writeL i xs = writeList i xs >> { xs } , write2L i ( xs , ys ) = writeList i ( xs ++ ys ) >> { ( xs , ys ) } , write3L i ( xs , ys , zs ) = writeList i ( xs ++ ys ++ zs ) >> { ( xs , ys , zs ) } . swap i j = read i >> = λ x → read j >> = λ y → write i y >> write j x . Fig. 1: Operations for reading and writing chunks of data.More operations defined in terms of read and write are shown in Figure 1,where xs abbreviates length xs . The function readList i n , where n is a naturalnumber, returns a list containing the n elements in the array starting from index i . Conversely, writeList i xs writes the list xs to the array with the first elementbeing at index i . In imperative programming we often store sequences of datainto an array and return the length of the data. Thus, functions writeL , write2L and write3L store lists into the array before returning their lengths. These read and write family of functions are used only in the specification; the algorithmwe construct should only mutate the array by swap ing elements.Among the many properties of readList and writeList that can be inducedfrom their definitions, the following will be used in a number of crucial steps: writeList i ( xs ++ ys ) = writeList i xs >> writeList ( i + xs ) ys . (9)A function f :: List a → m ( List a ) is said to be length preserving if f xs >> = λ ys → { ( ys , ys ) } = f xs >> = λ ys → { ( ys , xs ) } . It can be proved that perm ,and thus slowsort , are length preserving. On “composing monads”
In the sections to follow, some readers may have con-cern seeing perm , having class constraint
MonadPlus m , and some other codehaving constraint MonadArr e m in the same expression. This is totally fine:mixing two such subterms simply results in an expression having constraint(
MonadPlus m , MonadArr e m ). No lift ing is necessary.We use type classes to make it clear that we do not specify what exact monad perm is implemented with. It could be one monolithic monad, a monad built frommonad transformers [8], or a free monad interpreted by effect handlers [11]. Alltheorems and derivations about perm hold regardless of the actual monad, aslong as the monad satisfies all properties we demand.
While the list-based partition is relatively intuitive, partitioning an array in-place (that is, using at most O (1) additional space) is known to be a tricky phase of eclarative Pearl: Deriving Monadic Quicksort 9 array-based quicksort. Therefore we commence our discussion from deriving in-place array partitioning from the list version. The partition algorithm we endup deriving is known as the Lomuto scheme [2], as opposed to Hoare’s [9].
Specification
There are two issues to deal with before we present a specifica-tion for an imperative, array-based partitioning, based on list-based partition .Firstly, partition is not tail-recursive, while many linear-time array algorithmsare implemented as a tail-recursive for -loop. Thus we apply the standard trickconstructing a tail-recursive algorithm by introducing accumulating parameters.Define (we write the input/outputs of partition in bold font for clarity): partl :: Elm → ( List Elm × List Elm × List Elm ) → ( List Elm × List Elm ) partl p ( ys , zs , xs ) = let ( us , vs ) = partition p xs in ( ys ++ us , zs ++ vs ) . In words, partl p ( ys , zs , xs ) partitions xs into ( us , vs ) with respect to pivot p , but appends ys and zs respectively to us and vs . It is a generalisation of partition because partition p xs = partl p ([ ] , [ ] , xs ). By routine calculationexploiting associativity of (++), we can derive a tail-recursive definition of partl : partl p ( ys , zs , [ ]) = ( ys , zs ) partl p ( ys , zs , x : xs ) = if x p then partl p ( ys ++ [ x ] , zs , xs ) else partl p ( ys , zs ++ [ x ] , xs ) . It might aid our understanding if we note that, if we start partl with initial value([ ] , [ ] , xs ) we have the invariant that ys contains elements that are at most p ,and elements in zs are larger than p . The calculations below, however, do notrely on this observation. Our wish is to construct a variant of partl that works on arrays. That is,when the array contains ys ++ zs ++ xs , the three inputs to partl in a consecutivesegment, when the derived program finishes its work we wish to have ys ++ us ++ zs ++ vs , the output of partl , stored consecutively in the array.This brings us to the second issue: partition , and therefore partl , are stable(that is, elements in each partition retain their original order), which is a strongrequirement for array-based partitioning. It is costly to mutate ys ++ zs ++ xs into ys ++ us ++ zs ++ vs , since it demands that we retain the order of elementsin zs while inserting elements of us . For sorting we do not need such a strongpostcondition. It is sufficient, and can be done more efficiently, to mutate ys ++ zs ++ xs into ys ++ us ++ ws , where ws is some permutation of zs ++ vs . It is handyallowing non-determinism: we introduce a perm in our specification, indicatingthat we do not care about the order of elements in ws .Define second :: Monad m ⇒ ( b → m c ) → ( a , b ) → m ( a , c ), which appliesa monadic function to the second component of a tuple: It might be worth noting that partl causes a space leak in Haskell, since the accu-mulators become thunks that increase in size as the input list is traversed. It doesnot matter here since partl merely serves as a specification of ipartl .0 Shin-Cheng Mu and Tsung-Ju Chiang second f ( x , y ) = f y >> = λ y ′ → { ( x , y ′ ) } . Our new wish is to construct an array counterpart of second perm · partl p . Letthe function be ipartl :: ( MonadPlus m , MonadArr Elm m ) ⇒ Elm → Int → ( Nat × Nat × Nat ) → m ( Nat × Nat ) . The intention is that in a call ipartl p i ( ny , nz , nx ), p is the pivot, i the indexwhere ys ++ zs ++ xs is stored in the array, and ny , nz , nx respectively the lengthsof ys , zs , and xs . A specification of ipartl is: writeList i ( ys ++ zs ++ xs ) >> ipartl p i ( ys , zs , xs ) ⊆ second perm ( partl p ( ys , zs , xs )) >> = write2L i . That is, under assumption that ys ++ zs ++ xs is stored in the array starting fromindex i (initialised by writeList ), ipartl computes partl p ( ys , zs , xs ), possiblypermuting the second partition. The resulting two partitions are still stored inthe array starting from i , and their lengths are returned. Derivation
We start with fusing second perm into partl , that is, to construct partl ′ p ˙ ⊆ second perm · partl p . If we discover an inductive definition of partl ′ ,it can then be used to construct an inductive definition of ipartl . With someroutine calculation we get: partl ′ :: MonadPlus m ⇒ Elm → ( List Elm ) → m ( List Elm × List Elm ) partl ′ p ( ys , zs , [ ]) = { ( ys , zs ) } partl ′ p ( ys , zs , x : xs ) = if x p then perm zs >> = λ zs ′ → partl ′ p ( ys ++ [ x ] , zs ′ , xs ) else perm ( zs ++ [ x ]) >> = λ zs ′ → partl ′ p ( ys , zs ′ , xs ) . For an intuitive explanation, rather than permuting the second list zs aftercomputing partl , we can also permute zs in partl ′ before every recursive call.The specification of ipartl now becomes writeList i ( ys ++ zs ++ xs ) >> ipartl p i ( ys , zs , xs ) ⊆ partl ′ p ( ys , zs , xs ) >> = write2L i . (10)To calculate ipartl , we start with the right-hand side of ( ⊆ ), since it containsmore information to work with. We try to push write2L leftwards until the ex-pression has the form writeList i ( ys ++ zs ++ xs ) >> ... , thereby constructing ipartl . This is similar to that, in imperative program calculation, we work back-wards from the postcondition to construct a program that works under the givenprecondition [6]. We will discover a stronger specification partl ′ p ˙ ⊆ snd3 perm \ ( second perm · partl p ), where snd3 f ( x , y , z ) = f y >> = λ y ′ → { ( x , y ′ , z ) } . We omit the details.eclarative Pearl: Deriving Monadic Quicksort 11 We intend to construct ipartl by induction on xs . For xs :=[ ], we get ipartl p i ( ny , nz ,
0) = { ( ny , nz ) } . For the case x : xs , assume that the specification is metfor xs . Just for making the calculation shorter, we refactor partl ′ , lifting therecursive calls and turning the main body into an auxiliary function: partl ′ p ( ys , zs , x : xs ) = dispatch x p ( ys , zs , xs ) >> = partl ′ p , where dispatch x p ( ys , zs , xs ) = if x p then perm zs >> = λ zs ′ → { ( ys ++ [ x ] , zs ′ , xs ) } else perm ( zs ++ [ x ]) >> = λ zs ′ → { ( ys , zs ′ , xs ) } . We calculate: partl ′ p ( ys , zs , x : xs ) >> = write2L i = { definition of partl ′ } ( dispatch x p ( ys , zs , xs ) >> = partl ′ p ) >> = write2L i ⊇ { monad laws, inductive assumption } ( dispatch x p ( ys , zs , xs ) >> = write3L i ) >> = ipartl p i = { by (9), monad laws } dispatch x p ( ys , zs , xs ) >> = λ ( ys ′ , zs ′ , xs ) → writeList i ( ys ′ ++ zs ′ ) >> writeList ( i + ys ′ ++ zs ′ )) xs >> ipartl p i ( ys ′ , zs ′ , xs )= { perm preserves length, commutativity } writeList ( i + ys + zs + 1) xs >> dispatch x p ( ys , zs , xs ) >> = λ ( ys ′ , zs ′ , xs ) → writeList i ( ys ′ ++ zs ′ ) >> ipartl p i ( ys ′ , zs ′ , xs )= { definition of dispatch , function calls distribute into if } writeList ( i + ys + zs + 1) xs >> if x p then perm zs >> = λ zs ′ → writeList i ( ys ++ [ x ] ++ zs ′ ) >> ipartl p i ( ys + 1 , zs ′ , xs ) else perm ( zs ++ [ x ]) >> = λ zs ′ → writeList i ( ys ++ zs ′ ) >> ipartl p i ( ys , zs ′ , xs ) . We pause here to see what has happened: we have constructed a precondition writeList ( i + ys + zs + 1) xs , which is part of the desired precondition: writeList i ( ys ++ zs ++ ( x : xs )). To recover the latter precondition, we will try toturn both branches of if into the form writeList i ( ys ++ zs ++ [ x ]) >> = ... . Thatis, we try to construct, in both branches, some code that executes under theprecondition writeList i ( ys ++ zs ++ [ x ]) — that the code generates the correctresult is guaranteed by the refinement relation.It is easier for the second branch, where we can simply refine perm to {·} : perm ( zs ++ [ x ]) >> = λ zs ′ → writeList i ( ys ++ zs ′ ) >> ipartl p i ( ys , zs ′ , xs ) ⊇ { since { xs } ⊆ perm xs } writeList i ( ys ++ zs ++ [ x ]) >> ipartl p i ( ys , zs + 1 , xs ) . For the first branch, we focus on its first line: perm zs >> = λ zs ′ → writeList i ( ys ++ [ x ] ++ zs ′ )= { by (9), commutativity } writeList i ys >> perm zs >> = λ zs ′ → writeList ( i + ys ) ([ x ] ++ zs ′ ) ⊇ { introduce swap , see below } writeList i ys >> writeList ( i + ys ) ( zs ++ [ x ]) >> swap ( i + ys ) ( i + ys + zs )= { by (9) } writeList i ( ys ++ zs ++ [ x ]) >> swap ( i + ys ) ( i + ys + zs ) . Here we explain the last two steps. Operationally speaking, given an array con-taining ys ++ zs ++ [ x ] (the precondition we wanted, initialized by the writeList in the last line), how do we mutate it to ys ++ [ x ] ++ zs ′ (postcondition specifiedby the writeList in the first line), where zs ′ is a permutation of zs ? We may doso by swapping x with the leftmost element of zs , which is what we did in thesecond step. Formally, we used the property: perm zs >> = λ zs ′ → writeList i ([ x ] ++ zs ′ ) ⊇ writeList i ( zs ++ [ x ]) >> swap i ( i + zs ) . (11)Now that both branches are refined to code with precondition writeList i ( ys ++ zs ++ [ x ]), we go back to the main derivation: writeList ( i + ys + zs + 1) xs >> if x p then writeList i ( ys ++ zs ++ [ x ]) >> swap ( i + ys ) ( i + ys + zs ) >> ipartl p i ( ys + 1 , zs , xs ) else writeList i ( ys ++ zs ++ [ x ]) >> ipartl p i ( ys , zs + 1 , xs )= { distributivity of if , (9) } writeList i ( ys ++ zs ++ ( x : xs )) >> if x p then swap ( i + ys ) ( i + ys + zs ) >> ipartl p i ( ys + 1 , zs , xs ) else ipartl p i ( ys , zs + 1 , xs )= { write-read and definition of writeList } writeList i ( ys ++ zs ++ ( x : xs )) >> read ( i + ys + zs ) >> = λ x → if x p then swap ( i + ys ) ( i + ys + zs ) >> ipartl p i ( ys + 1 , zs , xs ) else ipartl p i ( ys , zs + 1 , xs ) . We have thus established the precondition writeList i ( ys ++ zs ++ ( x : xs )). Insummary, we have derived: ipartl :: MonadArr Elm m ⇒ Elm → Int → ( Int × Int × Int ) → m ( Int × Int ) ipartl p i ( ny , nz ,
0) = { ( ny , nz ) } ipartl p i ( ny , nz , k ) = read ( i + ny + nz ) >> = λ x → eclarative Pearl: Deriving Monadic Quicksort 13 if x p then swap ( i + ny ) ( i + ny + nz ) >> ipartl p i ( ny + 1 , nz , k ) else ipartl p i ( ny , nz + 1 , k ) . Now that we have ipartl derived, the rest of the work is to install it into quicksort.We intend to derive iqsort :: MonadArr Elm m ⇒ Int → Nat → m () such that isort i n sorts the n elements in the array starting from index i . We can give ita formal specification: writeList i xs >> iqsort i ( xs ) ⊆ slowsort xs >> = writeList i . (12)That is, when iqsort i is run from a state initialised by writeList i xs , it shouldbehave the same as slowsort xs >> = writeList i .The function iqsort can be constructed by induction on the length of theinput list. For the case xs := p : xs , we start from the left-hand side slowsort ( p : xs ) >> = writeList i and attempt to transform it to writeList i ( p : xs ) >> ... ,thereby construct iqsort . We present only the hightlights of the derivation.Firstly, slowsort ( p : xs ) >> = writeList i can be transformed to: partl ′ p ([ ] , [ ] , xs ) >> = λ ( ys , zs ) → perm ys >> = λ ys ′ → writeList i ( ys ′ ++ [ p ] ++ zs ) >> iqsort i ( ys ) >> iqsort ( i + ys + 1) ( zs ) . For that to work, we introduced two perm to permute both partitions gen-erated by partition . We can do so because perm > = > perm = perm and thus perm > = > slowsort = slowsort . The term perm zs was combined with partition p ,yielding partl ′ p , while perm ys will be needed later. We also needed (9) to split writeList i ( ys ′ ++ [ x ] ++ zs ′ ) into two parts. Assuming that (12) has been metfor lists shorter than xs , two subexpressions are folded back to iqsort .Now that we have introduced partl ′ , the next goal is to embed ipartl . Thestatus of the array before the two calls to iqsort is given by writeList i ( ys ′ ++[ p ] ++ zs ). That is, ys ′ ++ [ p ] ++ zs is stored in the array from index i , where ys ′ isa permutation of ys . The postcondition of ipartl , according to the specification(10), ends up with ys and zs stored consecutively. To connect the two conditions,we use a lemma that is dual to (11): perm ys >> = λ ys ′ → writeList i ( ys ′ ++ [ p ]) ⊇ writeList i ([ p ] ++ ys ) >> swap i ( i + ys ) . (13)This is what the typical quicksort algorithm does: swapping the pivot with thelast element of ys , and (13) says that it is valid because that is one of the manypermutations of ys . With (13) and (10), the specification can be refined to: writeList i ( p : xs ) >> ipartl p ( i + 1) (0 , , xs ) >> = λ ( ny , nz ) → swap i ( i + ny ) >> iqsort i ( ys ) >> iqsort ( i + ys + 1) ( zs ) . In summary, we have derived: iqsort :: MonadArr Elm m ⇒ Int → Nat → m () iqsort i { () } iqsort i n = read i >> = λ p → ipartl p ( i + 1) (0 , , n − >> = λ ( ny , nz ) → swap i ( i + ny ) >> iqsort i ny >> iqsort ( i + ny + 1) nz . From a specification of sorting using the non-determinism monad, we have de-rived a pure quicksort for lists and a state-monadic quicksort for arrays. We hopeto demonstrate that the monadic style is a good choice as a calculus for programderivation that involves non-determinism. One may perform the derivation inpointwise style, and deploy techniques that functional programmers have famil-iarised themselves with, such as pattern matching and induction on structuresor on sizes. When preferred, one can also work in point-free style with ( > = > ). Pro-grams having other effects can be naturally incorporated into this framework.The way we derive stateful programs echos how we, in Dijkstra’s style, reasonbackwards from the postcondition.A final note: ( > = > ) and ( ˙ ⊆ ) naturally induce the notion of (left) factor, ( \ ) ::( a → m b ) → ( a → m c ) → b → m c , defined by the Galois connection: f > = > g ˙ ⊆ h ≡ g ˙ ⊆ f \ h . Let h :: a → m c be a monadic specification, and f :: a → m b performs thecomputation half way, then f \ h is the most non-deterministic (least constrained)monadic program that, when ran after the postcondition set up by f , still meetsthe result specified by h . With ( \ ), ipartl and iqsort can be specified by: ipartl p i ˙ ⊆ write3L i \ (( second perm · partl p ) > = > write2L i ) , iqsort i ˙ ⊆ writeL i \ ( slowsort > = > writeList i ) . In relational calculus, the factor is an important operator that is often associ-ated with weakest precondition. We unfortunately cannot cover it due to spaceconstraints.
Acknowledgements
The authors would like to thank Jeremy Gibbons for thevaluable discussions during development of this work.
References
1. Backhouse, R.C., de Bruin, P.J., Malcolm, G., Voermans, E., van der Woude,J.: Relational catamorphisms. In: M¨oller, B. (ed.) Proceedings of the IFIPTC2/WG2.1 Working Conference on Constructing Programs. pp. 287–318. ElsevierScience Publishers (1991)eclarative Pearl: Deriving Monadic Quicksort 152. Bentley, J.L.: Programming Pearls, Second Edition. Addison-Wesley (2000)3. Bird, R.S.: Functional algorithm design. Science of Computer Programming ,15–31 (1996)4. Bird, R.S., de Moor, O.: Algebra of Programming. International Series in ComputerScience, Prentice Hall (1997)5. Bird, R.S., Rabe, F.: How to calculate with nondeterministic functions. In: Hutton,G. (ed.) Mathematics of Program Construction. pp. 138–154. Springer (2019)6. Dijkstra, E.W.: A Discipline of Programming. Prentice Hall (1976)7. Gibbons, J., Hinze, R.: Just do it: simple monadic equational reasoning. In: Danvy,O. (ed.) International Conference on Functional Programming. pp. 2–14. ACMPress (2011)8. Gill, A., Kmett, E.: The monad transformer library.https://hackage.haskell.org/package/mtl (2014)9. Hoare, C.A.R.: Algorithm 63: Partition. Communications of the ACM4