packages feed

singletons-2.2: tests/compile-and-dump/InsertionSort/InsertionSortImp.hs

{- InsertionSortImp.hs

(c) Richard Eisenberg 2012
eir@cis.upenn.edu

This file contains an implementation of insertion sort over natural numbers,
along with a Haskell proof that the sort algorithm is correct. The code below
uses a combination of GADTs and class instances to record the progress and
result of the proof.

Ideally, the GADTs would be defined so that the constructors take no explicit
parameters --- the information would all be encoded in the constraints to the
constructors. However, due to the nature of the permutation relation, a class
instance definition corresponding to the constructor PermIns would require
existentially-quantified type variables (the l2 variable in the declaration of
PermIns). Type variables in an instance constraint but not mentioned in the
instance head are inherently ambiguous. The compiler would never be able to
infer the value of the variables. Thus, it is not possible to make a class
PermutationC analogous to PermutationProof in the way that AscendingC is
analogous to AscendingProof. (Note that it may be possible to fundamentally
rewrite the inductive definition of the permutation relation to avoid
existentially-quantified variables. We have not attempted that here.)

If there were a way to offer an explicit dictionary when satisfying a constraint,
this problem could be avoided, as the variable in question could be made
unambiguous.

-}

{-# LANGUAGE IncoherentInstances, ConstraintKinds, TypeFamilies,
             TemplateHaskell, RankNTypes, ScopedTypeVariables, GADTs,
             TypeOperators, DataKinds, PolyKinds, MultiParamTypeClasses,
             FlexibleContexts, FlexibleInstances, UndecidableInstances #-}

module InsertionSort.InsertionSortImp where

import Data.Kind (type (*))
import Data.Singletons.Prelude
import Data.Singletons.SuppressUnusedWarnings
import Data.Singletons.TH

data Dict c where
  Dict :: c => Dict c

-- Natural numbers, defined with singleton counterparts
$(singletons [d|
  data Nat = Zero | Succ Nat
  |])

-- convenience functions for testing purposes
toNat :: Int -> Nat
toNat 0         = Zero
toNat n | n > 0 = Succ (toNat (n - 1))
toNat _         = error "Converting negative to Nat"

fromNat :: Nat -> Int
fromNat Zero = 0
fromNat (Succ n) = 1 + (fromNat n)

-- A less-than-or-equal relation among naturals
class (a :: Nat) :<=: (b :: Nat)
instance Zero :<=: a
instance (a :<=: b) => (Succ a) :<=: (Succ b)

-- A proof term asserting that a list of naturals is in ascending order
data AscendingProof :: [Nat] -> * where
  AscEmpty :: AscendingProof '[]
  AscOne :: AscendingProof '[n]
  AscCons :: (a :<=: b, AscendingC (b ': rest)) => AscendingProof (a ': b ': rest)

-- The class constraint (implicit parameter definition) corresponding to
-- AscendingProof
class AscendingC (lst :: [Nat]) where
  ascendingProof :: AscendingProof lst

-- The instances correspond to the constructors of AscendingProof
instance AscendingC '[] where
  ascendingProof = AscEmpty
instance AscendingC '[n] where
  ascendingProof = AscOne
instance (a :<=: b, AscendingC (b ': rest)) => AscendingC (a ': b ': rest) where
  ascendingProof = AscCons

-- A proof term asserting that l2 is the list produced when x is inserted
-- (anywhere) into list l1
data InsertionProof (x :: k) (l1 :: [k]) (l2 :: [k]) where
  InsHere :: InsertionProof x l (x ': l)
  InsLater :: InsertionC x l1 l2 => InsertionProof x (y ': l1) (y ': l2)

-- The class constraint corresponding to InsertionProof
class InsertionC (x :: k) (l1 :: [k]) (l2 :: [k]) where
  insertionProof :: InsertionProof x l1 l2

instance InsertionC x l (x ': l) where
  insertionProof = InsHere
instance InsertionC x l1 l2 => InsertionC x (y ': l1) (y ': l2) where
  insertionProof = InsLater

-- A proof term asserting that l1 and l2 are permutations of each other
data PermutationProof (l1 :: [k]) (l2 :: [k]) where
  PermId :: PermutationProof l l
  PermIns :: InsertionC x l2 l2' => PermutationProof l1 l2 ->
               PermutationProof (x ': l1) l2'

-- Here is the definition of insertion sort about which we will be reasoning:
$(singletons [d|
  leq :: Nat -> Nat -> Bool
  leq Zero _ = True
  leq (Succ _) Zero = False
  leq (Succ a) (Succ b) = leq a b

  insert :: Nat -> [Nat] -> [Nat]
  insert n [] = [n]
  insert n (h:t) = if leq n h then (n:h:t) else h:(insert n t)

  insertionSort :: [Nat] -> [Nat]
  insertionSort [] = []
  insertionSort (h:t) = insert h (insertionSort t)
  |])

-- A lemma that states if sLeq a b is STrue, then (a :<=: b)
-- This is necessary to convert from the boolean definition of <= to the
-- corresponding constraint
sLeq_true__le :: (Leq a b ~ True) => SNat a -> SNat b -> Dict (a :<=: b)
sLeq_true__le a b = case (a, b) of
  (SZero, SZero) -> Dict
  (SZero, SSucc _) -> Dict
  -- (SSucc _, SZero) -> undefined <== IMPOSSIBLE
  (SSucc a', SSucc b') -> case sLeq_true__le a' b' of
    Dict -> Dict

-- A lemma that states if sLeq a b is SFalse, then (b :<=: a)
sLeq_false__nle :: (Leq a b ~ False) => SNat a -> SNat b -> Dict (b :<=: a)
sLeq_false__nle a b = case (a, b) of
  -- (SZero, SZero) -> undefined <== IMPOSSIBLE
  -- (SZero, SSucc _) -> undefined <== IMPOSSIBLE
  (SSucc _, SZero) -> Dict
  (SSucc a', SSucc b') -> case sLeq_false__nle a' b' of
    Dict -> Dict

-- A lemma that states that inserting into an ascending list produces an
-- ascending list
insert_ascending :: forall n lst.
  AscendingC lst => SNat n -> SList lst -> Dict (AscendingC (Insert n lst))
insert_ascending n lst =
  case ascendingProof :: AscendingProof lst of
    AscEmpty -> Dict -- If lst is empty, then we're done
    AscOne -> case lst of -- If lst has one element...
      -- SNil -> undefined <== IMPOSSIBLE
      SCons h _ -> case sLeq n h of -- then check if n is <= h
        STrue -> case sLeq_true__le n h of Dict -> Dict -- if so, we're done
        SFalse -> case sLeq_false__nle n h of Dict -> Dict -- if not, we're done
    AscCons -> case lst of -- Otherwise, if lst is more than one element...
      -- SNil -> undefined <== IMPOSSIBLE
      SCons h t -> case sLeq n h of -- then check if n is <= h
        STrue -> case sLeq_true__le n h of Dict -> Dict -- if so, we're done
        SFalse -> case sLeq_false__nle n h of -- if not, things are harder...
          Dict -> case t of -- destruct t: lst is (h : h2 : t2)
            -- SNil -> undefined <== IMPOSSIBLE
            SCons h2 _ -> case sLeq n h2 of -- is n <= h2?
              STrue -> -- if so, we're done
                case sLeq_true__le n h2 of Dict -> Dict
              SFalse -> -- otherwise, show that (Insert n t) is sorted
                case insert_ascending n t of Dict -> Dict -- and we're done

-- A lemma that states that inserting n into lst produces a new list with n
-- inserted into lst.
insert_insertion :: SNat n -> SList lst -> Dict (InsertionC n lst (Insert n lst))
insert_insertion n lst =
  case lst of
    SNil -> Dict -- if lst is empty, we're done
    SCons h t -> case sLeq n h of -- otherwise, is n <= h?
      STrue -> Dict -- if so, we're done
      SFalse -> case insert_insertion n t of Dict -> Dict -- otherwise, recur

-- A lemma that states that the result of an insertion sort is in ascending order
insertionSort_ascending :: SList lst -> Dict (AscendingC (InsertionSort lst))
insertionSort_ascending lst = case lst of
  SNil -> Dict -- if the list is empty, we're done

  -- otherwise, we recur to find that insertionSort on t produces an ascending list,
  -- and then we use the fact that inserting into an ascending list produces an
  -- ascending list
  SCons h t -> case insertionSort_ascending t of
    Dict -> case insert_ascending h (sInsertionSort t) of Dict -> Dict

-- A lemma that states that the result of an insertion sort is a permutation
-- of its input
insertionSort_permutes :: SList lst -> PermutationProof lst (InsertionSort lst)
insertionSort_permutes lst = case lst of
  SNil -> PermId -- if the list is empty, we're done

  -- otherwise, we wish to use PermIns. We must know that t is a permutation of
  -- the insertion sort of t and that inserting h into the insertion sort of t
  -- works correctly:
  SCons h t ->
    case insert_insertion h (sInsertionSort t) of
      Dict -> PermIns (insertionSort_permutes t)

-- A theorem that states that the insertion sort of a list is both ascending
-- and a permutation of the original
insertionSort_correct :: SList lst -> (Dict (AscendingC (InsertionSort lst)),
                                       PermutationProof lst (InsertionSort lst))
insertionSort_correct lst = (insertionSort_ascending lst,
                             insertionSort_permutes lst)