diff options
author | Simon Peyton Jones <simonpj@microsoft.com> | 2014-08-28 10:34:17 +0100 |
---|---|---|
committer | Simon Peyton Jones <simonpj@microsoft.com> | 2014-08-28 17:51:13 +0100 |
commit | 94926b113f0f2669792cc2d85f29c348cfa291c9 (patch) | |
tree | efc657148ae3379133a718321650e1e758b38785 | |
parent | 75d998bfad7433ba000236dfd07e386c95f2b769 (diff) | |
download | haskell-94926b113f0f2669792cc2d85f29c348cfa291c9.tar.gz |
Add an interesting type-family/GADT example of deletion for red-black trees
Due to Stephanie Weirich, Dan Licata, John Hughes, Matt Might
-rw-r--r-- | testsuite/tests/indexed-types/should_compile/all.T | 1 | ||||
-rw-r--r-- | testsuite/tests/indexed-types/should_compile/red-black.hs | 327 |
2 files changed, 328 insertions, 0 deletions
diff --git a/testsuite/tests/indexed-types/should_compile/all.T b/testsuite/tests/indexed-types/should_compile/all.T index 016444a138..95a55572d5 100644 --- a/testsuite/tests/indexed-types/should_compile/all.T +++ b/testsuite/tests/indexed-types/should_compile/all.T @@ -245,3 +245,4 @@ test('T8978', normal, compile, ['']) test('T8979', normal, compile, ['']) test('T9085', normal, compile, ['']) test('T9316', normal, compile, ['']) +test('red-black', normal, compile, ['']) diff --git a/testsuite/tests/indexed-types/should_compile/red-black.hs b/testsuite/tests/indexed-types/should_compile/red-black.hs new file mode 100644 index 0000000000..9873463ec0 --- /dev/null +++ b/testsuite/tests/indexed-types/should_compile/red-black.hs @@ -0,0 +1,327 @@ +{-# LANGUAGE InstanceSigs,GADTs, DataKinds, KindSignatures, MultiParamTypeClasses, FlexibleInstances, TypeFamilies #-} + +-- Implementation of deletion for red black trees by Matt Might +-- Editing to preserve the red/black tree invariants by Stephanie Weirich, +-- Dan Licata and John Hughes, at WG2.8 August 2014 + +-- Original available from: +-- http://matt.might.net/articles/red-black-delete/code/RedBlackSet.hs +-- Slides: +-- http://matt.might.net/papers/might2014redblack-talk.pdf + +module MightRedBlackGADT where + +import Prelude hiding (max) +-- import Test.QuickCheck hiding (elements) +import Data.List(nub,sort) +import Control.Monad(liftM) +import Data.Type.Equality +import Data.Maybe(isJust) + +-- +data Color = Red | Black | DoubleBlack | NegativeBlack + +-- Singleton type, connects the type level color to data +-- not necessary in a full-spectrum dependently-typed language +data SColor (c :: Color) where + R :: SColor Red -- red + B :: SColor Black -- black + BB :: SColor DoubleBlack -- double black + NB :: SColor NegativeBlack -- negative black + + +-- natural numbers for tracking the black height +data Nat = Z | S Nat + +-- Well-formed Red/Black trees +-- n statically tracks the black height of the tree +-- c statically tracks the color of the root node +data CT (n :: Nat) (c :: Color) (a :: *) where + E :: CT Z Black a + T :: Valid c c1 c2 => + SColor c -> (CT n c1 a) -> a -> (CT n c2 a) -> CT (Incr c n) c a + +-- this type class forces the children of red nodes to be black +-- and also excludes double-black and negative-black nodes from +-- a valid red-black tree. +class Valid (c :: Color) (c1 :: Color) (c2 :: Color) where +instance Valid Red Black Black +instance Valid Black c1 c2 + +-- incrementing the black height, based on the color +type family Incr (c :: Color) (n :: Nat) :: Nat +type instance Incr Black n = S n +type instance Incr DoubleBlack n = S (S n) +type instance Incr Red n = n +type instance Incr NegativeBlack (S n) = n + +-- the top level type of red-black trees +-- the data constructor forces the root to be black and +-- hides the black height. +data RBSet a where + Root :: (CT n Black a) -> RBSet a + +-- We can't automatically derive show and equality +-- methods for GADTs. +instance Show (SColor c) where + show R = "R" + show B = "B" + show BB = "BB" + show NB = "NB" + +instance Show a => Show (CT n c a) where + show E = "E" + show (T c l x r) = + "(T " ++ show c ++ " " ++ show l ++ " " + ++ show x ++ " " ++ show r ++ ")" +instance Show a => Show (RBSet a) where + show (Root x) = show x + + +showT :: CT n c a -> String +showT E = "E" +showT (T c l x r) = + "(T " ++ show c ++ " " ++ showT l ++ " " + ++ "..." ++ " " ++ showT r ++ ")" + + +-- the test equality class gives us a proof that type level +-- colors are equal. +-- testEquality :: SColor c1 -> SColor c2 -> Maybe (c1 ~ c2) +instance TestEquality SColor where + testEquality R R = Just Refl + testEquality B B = Just Refl + testEquality BB BB = Just Refl + testEquality NB NB = Just Refl + testEquality _ _ = Nothing + +-- comparing two Red/Black trees for equality. +-- unfortunately, because we have two instances, we can't +-- use the testEquality class. +(%==%) :: Eq a => CT n1 c1 a -> CT n2 c2 a -> Bool +E %==% E = True +T c1 a1 x1 b1 %==% T c2 a2 x2 b2 = + isJust (testEquality c1 c2) && a1 %==% a2 && x1 == x2 && b1 %==% b2 + +instance Eq a => Eq (RBSet a) where + (Root t1) == (Root t2) = t1 %==% t2 + + + -- Public operations -- + +empty :: RBSet a +empty = Root E + +member :: (Ord a) => a -> RBSet a -> Bool +member x (Root t) = aux x t where + aux :: Ord a => a -> CT n c a -> Bool + aux x E = False + aux x (T _ l y r) | x < y = aux x l + | x > y = aux x r + | otherwise = True + +elements :: Ord a => RBSet a -> [a] +elements (Root t) = aux t [] where + aux :: Ord a => CT n c a -> [a] -> [a] + aux E acc = acc + aux (T _ a x b) acc = aux a (x : aux b acc) + + -- INSERTION -- + +insert :: (Ord a) => a -> RBSet a -> RBSet a +insert x (Root s) = blacken (ins x s) + where ins :: Ord a => a -> CT n c a -> IR n a + ins x E = IN R E x E + ins x s@(T color a y b) | x < y = balanceL color (ins x a) y b + | x > y = balanceR color a y (ins x b) + | otherwise = (IN color a y b) + + + blacken :: IR n a -> RBSet a + blacken (IN _ a x b) = Root (T B a x b) + +-- an intermediate data structure that temporarily violates the +-- red-black tree invariants during insertion. (IR stands for infra-red). +-- This tree must be non-empty, but is allowed to have two red nodes in +-- a row. +data IR n a where + IN :: SColor c -> CT n c1 a -> a -> CT n c2 a -> IR (Incr c n) a + +-- `balance` rotates away coloring conflicts +-- we separate it into two cases based on whether the infraction could +-- be on the left or the right + +balanceL :: SColor c -> IR n a -> a -> CT n c1 a -> IR (Incr c n) a +balanceL B (IN R (T R a x b) y c) z d = IN R (T B a x b) y (T B c z d) +balanceL B (IN R a x (T R b y c)) z d = IN R (T B a x b) y (T B c z d) +balanceL c (IN B a x b) z d = IN c (T B a x b) z d +balanceL c (IN R a@(T B _ _ _) x b@(T B _ _ _)) z d = IN c (T R a x b) z d +balanceL c (IN R a@E x b@E) z d = IN c (T R a x b) z d + + +balanceR :: SColor c -> CT n c1 a -> a -> IR n a -> IR (Incr c n) a +balanceR B a x (IN R (T R b y c) z d) = IN R (T B a x b) y (T B c z d) +balanceR B a x (IN R b y (T R c z d)) = IN R (T B a x b) y (T B c z d) +balanceR c a x (IN B b z d) = IN c a x (T B b z d) +balanceR c a x (IN R b@(T B _ _ _) z d@(T B _ _ _)) = IN c a x (T R b z d) +balanceR c a x (IN R b@E z d@E) = IN c a x (T R b z d) + + + -- DELETION -- + +-- like insert, delete relies on a helper function that may (slightly) +-- violate the red-black tree invariant + +delete :: (Ord a) => a -> RBSet a -> RBSet a +delete x (Root s) = blacken (del x s) + where blacken :: DT n a -> RBSet a + blacken (DT B a x b) = Root (T B a x b) + blacken (DT BB a x b) = Root (T B a x b) + blacken DE = Root E + blacken DEE = Root E + -- note: del always produces a black or + -- double black tree. These last two cases are unreachable + -- but it may be difficult to prove it. + blacken (DT R a x b) = Root (T B a x b) + blacken (DT NB a x b) = Root (T B a x b) + + del :: Ord a => a -> CT n c a -> DT n a + del x E = DE + del x s@(T color a y b) | x < y = bubbleL color (del x a) y b + | x > y = bubbleR color a y (del x b) + | otherwise = remove s + + +-- deletion tree, the result of del +-- could have a double black node +-- (but only at the top or as a single leaf) +data DT n a where + DT :: SColor c -> CT n c1 a -> a -> CT n c2 a -> DT (Incr c n) a + DE :: DT Z a + DEE :: DT (S Z) a + +-- Note: we could unify this with the IR data structure by +-- adding the DE and DEE data constructors. That would allow us to +-- reuse the same balancing function (and blacken function) +-- for both insertion and deletion. +-- However, if an Agda version did that it might get into trouble, +-- trying to show that insertion never produces a leaf. + + +instance Show a => Show (DT n a) where + show DE = "DE" + show DEE = "DEE" + show (DT c l x r) = + "(DT " ++ show c ++ " " ++ show l ++ " " + ++ "..." ++ " " ++ show r ++ ")" + +-- Note: eliding a to make error easier below. +showDT :: DT n a -> String +showDT DE = "DE" +showDT DEE = "DEE" +showDT (DT c l x r) = + "(DT " ++ show c ++ " " ++ showT l ++ " " + ++ "..." ++ " " ++ showT r ++ ")" + + +-- maximum element from a red-black tree +max :: CT n c a -> a +max E = error "no largest element" +max (T _ _ x E) = x +max (T _ _ x r) = max r + +-- Remove the top node: it might leave behind a double black node +-- if the removed node is black (note that the height is preserved) +remove :: CT n c a -> DT n a +remove (T R E _ E) = DE +remove (T B E _ E) = DEE +remove (T B E _ (T R a x b)) = DT B a x b +remove (T B (T R a x b) _ E) = DT B a x b +remove (T color l y r) = bubbleL color (removeMax l) (max l) r + +-- remove the maximum element of the tree +removeMax :: CT n c a -> DT n a +removeMax E = error "no maximum to remove" +removeMax s@(T _ _ _ E) = remove s +removeMax s@(T color l x r) = bubbleR color l x (removeMax r) + +-- make a node more red (i.e. decreasing its black height) +-- note that the color of the resulting node is not part of the +-- result height because the result of this operation doesn't +-- necessarily satisfy the RBT invariants +redden :: CT (S n) c a -> DT n a +redden (T B a x y) = DT R a x y +redden (T BB a x y) = DT B a x y +redden (T R a x y) = DT NB a x y + +-- assert that a tree is red. Dynamically fail otherwise +-- this is a cheat that Haskell let's us get away with, but Agda +-- would not +assertRed :: DT n a -> CT n Red a +assertRed (DT R (T B aa ax ay) x (T B ac az ad)) = + (T R (T B aa ax ay) x (T B ac az ad)) +assertRed t = error ("not a red tree " ++ showDT t) + +dbalanceL :: SColor c -> DT n a -> a -> CT n c1 a -> DT (Incr c n) a +-- reshuffle (same as insert) +dbalanceL B (DT R (T R a x b) y c) z d = DT R (T B a x b) y (T B c z d) +dbalanceL B (DT R a x (T R b y c)) z d = DT R (T B a x b) y (T B c z d) +-- double-black (new for delete) +dbalanceL BB (DT R (T R a x b) y c) z d = DT B (T B a x b) y (T B c z d) +dbalanceL BB (DT R a x (T R b y c)) z d = DT B (T B a x b) y (T B c z d) +dbalanceL BB (DT NB a@(T B _ _ _) x (T B b y c)) z d = + case (dbalanceL B (redden a) x b) of + r@(DT R _ _ _) -> DT B (assertRed r) y (T B c z d) + (DT B a1 x1 y1) -> DT B (T B a1 x1 y1) y (T B c z d) +-- fall through cases (same as insert) +dbalanceL c (DT B a x b) z d = DT c (T B a x b) z d +dbalanceL c (DT R a@(T B _ _ _) x b@(T B _ _ _)) z d = DT c (T R a x b) z d +dbalanceL c (DT R a@E x b@E) z d = DT c (T R a x b) z d +-- more fall through cases (new for delete) +dbalanceL c DE x b = (DT c E x b) +dbalanceL c a x b = error ("no case for " ++ showDT a) + + +dbalanceR :: SColor c -> CT n c1 a -> a -> DT n a -> DT (Incr c n) a +dbalanceR B a x (DT R (T R b y c) z d) = DT R (T B a x b) y (T B c z d) +dbalanceR B a x (DT R b y (T R c z d)) = DT R (T B a x b) y (T B c z d) +dbalanceR BB a x (DT R (T R b y c) z d) = DT B (T B a x b) y (T B c z d) +dbalanceR BB a x (DT R b y (T R c z d)) = DT B (T B a x b) y (T B c z d) +dbalanceR BB a x (DT NB (T B b y c) z d@(T B _ _ _)) = + case (dbalanceR B c z (redden d)) of + r@(DT R _ _ _) -> DT B (T B a x b) y (assertRed r) + (DT B a1 x1 y1) -> DT B (T B a x b) y (T B a1 x1 y1) + +dbalanceR c a x (DT B b z d) = DT c a x (T B b z d) +dbalanceR c a x (DT R b@(T B _ _ _) z d@(T B _ _ _)) = DT c a x (T R b z d) +dbalanceR c a x (DT R b@E z d@E) = DT c a x (T R b z d) +dbalanceR c a x DE = DT c a x E +dbalanceR c a x b = error ("no case for " ++ showDT b) + + +bubbleL :: SColor c -> DT n a -> a -> CT n c1 a -> DT (Incr c n) a +-- don't want to prove generally that +-- (Incr (Blacker c) 'Z ~ Incr c ('S 'Z)) so expand by cases. +-- (Note: Do we need all three of these cases?) +bubbleL B DEE x r = dbalanceR BB E x (redden r) +bubbleL R DEE x r = dbalanceR B E x (redden r) +bubbleL NB DEE x r = dbalanceR R E x (redden r) +-- same thing here +bubbleL B l@(DT BB a y b) x r = dbalanceR BB (T B a y b) x (redden r) +bubbleL R l@(DT BB a y b) x r = dbalanceR B (T B a y b) x (redden r) +bubbleL NB l@(DT BB a y b) x r = dbalanceR R (T B a y b) x (redden r) +-- fall through case +bubbleL color l x r = dbalanceL color l x r + + +bubbleR :: SColor c -> CT n c1 a -> a -> DT n a -> DT (Incr c n) a +bubbleR B r x DEE = dbalanceL BB (redden r) x E +bubbleR R r x DEE = dbalanceL B (redden r) x E +bubbleR NB r x DEE = dbalanceL R (redden r) x E +-- same thing here +bubbleR B r x l@(DT BB a y b) = dbalanceL BB (redden r) x (T B a y b) +bubbleR R r x l@(DT BB a y b) = dbalanceL B (redden r) x (T B a y b) +bubbleR NB r x l@(DT BB a y b) = dbalanceL R (redden r) x (T B a y b) +-- fall through case +bubbleR color l x r = dbalanceR color l x r + |