IOSpec-0.1: examples/Queues.hs
{-# OPTIONS_GHC -fglasgow-exts #-}
import Test.QuickCheck
import Test.IOSpec.IORef
import Data.Dynamic
import Control.Monad
-- We begin by giving an implementation of queues using our pure
-- specification of IORefs.
type Queue = (IORef Data, IORef Data)
data Data = Cell Int (IORef Data) | NULL deriving Typeable
-- There is one important point here. To use the IORefs in IOSpec,
-- we need to make sure that any data we store in an IORef is an
-- instance of Typeable. Fortunately, GHC can derive instances of
-- Typeable for most data types.
-- The implementation of Queues is fairly standard. We use a linked
-- list, with special pointers to the head and tail of the queue.
emptyQueue :: IOState Queue
emptyQueue = do
front <- newIORef NULL
back <- newIORef NULL
return (front,back)
enqueue :: Queue -> Int -> IOState ()
enqueue (front,back) x =
do newBack <- newIORef NULL
let cell = Cell x newBack
c <- readIORef back
writeIORef back cell
case c of
NULL -> writeIORef front cell
Cell y t -> writeIORef t cell
dequeue :: Queue -> IOState (Maybe Int)
dequeue (front,back) = do
c <- readIORef front
case c of
NULL -> return Nothing
(Cell x nextRef) -> do
next <- readIORef nextRef
writeIORef front next
return (Just x)
-- Besides basic queue operations, we also implement queue reversal.
reverseQueue :: Queue -> IOState ()
reverseQueue (front,back) = do
f <- readIORef front
case f of
NULL -> return ()
Cell x nextRef -> do
flipPointers NULL (Cell x nextRef)
f <- readIORef front
b <- readIORef back
writeIORef front b
writeIORef back f
flipPointers :: Data -> Data -> IOState ()
flipPointers prev NULL = return ()
flipPointers prev (Cell x next) = do
nextCell <- readIORef next
writeIORef next prev
flipPointers (Cell x next) nextCell
-- A pair of functions that convert lists to queues and vice versa.
queueToList :: Queue -> IOState [Int]
queueToList = unfoldM dequeue
listToQueue :: [Int] -> IOState Queue
listToQueue xs = do q <- emptyQueue
sequence_ (map (enqueue q) xs)
return q
unfoldM :: Monad m => (a -> m (Maybe x)) -> a -> m [x]
unfoldM f a = do
x <- f a
case x of
Nothing -> return []
Just x -> liftM (x:) (unfoldM f a)
-- Now we can state a few properties of queues.
inversesProp :: [Int] -> Bool
inversesProp xs = xs == runIOState (listToQueue xs >>= queueToList)
revRevProp xs = runIOState revRevProg == xs
where
revRevProg = do q <- listToQueue xs
reverseQueue q
reverseQueue q
queueToList q
revProp xs = runIOState revProg == reverse xs
where
revProg = do q <- listToQueue xs
reverseQueue q
queueToList q
queueProp1 x = runIOState queueProg1 == Just x
where
queueProg1 = do q <- emptyQueue
enqueue q x
dequeue q
queueProp2 x y = runIOState queueProg2 == Just y
where
queueProg2 = do q <- emptyQueue
enqueue q x
enqueue q y
dequeue q
dequeue q
main = do putStrLn "Testing first queue property..."
quickCheck queueProp1
putStrLn "Testing second queue property..."
quickCheck queueProp2
putStrLn "Testing queueToList and listToQueue.."
quickCheck inversesProp
putStrLn "Testing that reverseQueue is its own inverse..."
quickCheck revRevProp
putStrLn "Testing reverseQueue matches the spec..."
quickCheck revProp
-- Once we are satisfied with our implementation, we can import the
-- "real" Data.IORef instead of Test.IOSpec.IORef.