3

I've been trying to reproduce an aside mentioned in All Sorts of Permutations (Functional Pearl) by Christiansen, Danilenko and Dylus, a paper for the upcoming ICFP 2016. Section 8 (“Final Remarks”) claims that by choosing a particular non-deterministic predicate, a monadic merge sort can produce all permutations of a sequence in lexicographical order.

We did only consider the non-deterministic predicate coinCmp, while there are other non-deterministic predicates that can be used to affect the order of enumeration. For example, the following function lifts a predicate cmp to a non-deterministic context.

liftCmp :: MonadPlus μ
        ⇒ (α → α → Bool) → Cmp α μ
liftCmp p x y = return (p x y) ⊕ return (not (p x y))

When we use this function to lift a comparison function and pass it to a monadic version of merge sort, we get a special kind of permutation function: it enumerates permutations in lexicographical order.

I'm pretty sure what I've written here is merge sort, but when run the ordering isn't as advertised.

import Control.Applicative (Alternative((<|>)))
import Control.Monad (MonadPlus, join)
import Data.Functor.Identity (Identity)

-- Comparison in a context
type Comparison a m = a -> a -> m Bool

-- Ordering lifted into the Boring Monad
boringCmp :: (a -> a -> Bool) -> Comparison a Identity
boringCmp p x y = return (p x y)

-- Arbitrary ordering in a non-deterministic context
cmp :: MonadPlus m => Comparison a m
cmp _ _ = return True <|> return False

-- Ordering lifted into a non-deterministic context
liftCmp :: MonadPlus m => (a -> a -> Bool) -> Comparison a m
liftCmp p x y = let b = p x y in return b <|> return (not b)

mergeM :: Monad m => Comparison a m -> [a] -> [a] -> m [a]
mergeM _ ls         []         = return ls
mergeM _ []         rs         = return rs
mergeM p lls@(l:ls) rrs@(r:rs) = do
    b <- p l r
    if b
    then (l:) <$> mergeM p ls rrs
    else (r:) <$> mergeM p lls rs

mergeSortM :: Monad m => Comparison a m -> [a] -> m [a]
mergeSortM _ []  = return []
mergeSortM _ [x] = return [x]
mergeSortM p xs  = do
    let (ls, rs) = deinterleave xs
    join $ mergeM p <$> mergeSortM p ls <*> mergeSortM p rs
  where
    deinterleave :: [a] -> ([a], [a])
    deinterleave [] = ([], [])
    deinterleave [l] = ([l], [])
    deinterleave (l:r:xs) = case deinterleave xs of (ls, rs) -> (l:ls, r:rs)
λ mergeSortM (boringCmp (<=)) [2,1,3] :: Identity [Int]
Identity [1,2,3]

λ mergeSortM cmp [2,1,3] :: [[Int]]
[[2,3,1],[2,1,3],[1,2,3],[3,2,1],[3,1,2],[1,3,2]]

λ mergeSortM (liftCmp (<=)) [2,1,3] :: [[Int]]
[[1,2,3],[2,1,3],[2,3,1],[1,3,2],[3,1,2],[3,2,1]]

And the actual lexicographic ordering for reference—

λ sort it
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
duplode
  • 33,731
  • 7
  • 79
  • 150
R B
  • 1,109
  • 9
  • 13

1 Answers1

1

Let's try a variant of deinterleave, which splits the first and last half of the list, instead of splitting even- and odd- indexed elements as in the posted code:

deinterleave :: [a] -> ([a], [a])
deinterleave ys = splitAt (length ys `div` 2) ys

Result:

> mergeSortM (liftCmp (<=)) [2,1,3] :: [[Int]]
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

Unfortunately, this does not solve the issue as I first hoped, as Rowan Blush points out below. :-/

chi
  • 111,837
  • 3
  • 133
  • 218
  • 1
    That's just chance, see `mergeSortM (liftCmp (<=)) [1,2,3] :: [[Int]]`⇒ `[[1,2,3],[2,1,3],[2,3,1],[1,3,2],[3,1,2],[3,2,1]]`. – R B Aug 07 '16 at 18:04
  • @RowanBlush Interesting. Still, the definition of `deinterleave` does affect the end result. I wonder which one was used by the authors -- and indeed, if there's a sensible definition which really produces the intended behaviour. – chi Aug 07 '16 at 18:11