-- | Proper documentation is TBD module Data.Coolean ( Cool(..) -- * Overloaded parallel operators , Coolean(..) , true, false, nott , (&&&), (|||), (==>) -- * Overloaded sequential operators , (!&&), (!||), (!=>) -- * Consumers , Sched(..), sched0 , run, lookahead, par, subsetsc ) where import Control.Exception import Data.IORef import System.IO.Unsafe -- import Data.Tree -- | Concurrent booleans. Writing properties with the Cool data type often yields faster searches compared to Bool. data Cool = Atom Bool | Not Cool | And Cool Cool -- | Sequential conjunction, the second operator is not evaluated unless the first is true. | Seq Cool Cool deriving Show -- Class based construction true :: Cool true = Atom True false :: Cool false = Atom False -- | Commutative conjunction (&&&) :: (Coolean a, Coolean b) => a -> b -> Cool a &&& b = toCool a <&> toCool b infixr 3 &&& -- | Commutative disjunction (|||) :: (Coolean a, Coolean b) => a -> b -> Cool a ||| b = toCool a <||> toCool b infixr 2 ||| -- | Negation nott :: Coolean a => a -> Cool nott a = Not (toCool a) -- | Parallel implication (==>) :: (Coolean a, Coolean b) => a -> b -> Cool a ==> b = Not (toCool a <&> Not (toCool b)) infixr 0 ==> -- | Sequential conjunction. Does not evaluate second operand unless first is True. (!&&) :: (Coolean a, Coolean b) => a -> b -> Cool a !&& b = toCool a `Seq` toCool b -- | Sequential disjunction. Does not evaluate second operand unless first is True. (!||) :: (Coolean a, Coolean b) => a -> b -> Cool a !|| b = Not (Not (toCool a) `Seq` Not (toCool b)) -- | Sequential implication. Does not evaluate second operand unless first is True. (!=>) :: (Coolean a, Coolean b) => a -> b -> Cool a !=> b = Not (toCool a `Seq` Not (toCool b)) -- | Provides better interoperability between Bool and Cool by overloading operators. class Coolean b where toCool :: b -> Cool toBool :: b -> Bool isCool :: (a -> b) -> Bool instance Coolean Cool where toCool = id toBool (And a b) = toBool a && toBool b toBool (Seq a b) = toBool a && toBool b toBool (Not a) = not (toBool a) toBool (Atom a) = a isCool _ = True instance Coolean Bool where toCool = Atom toBool = id isCool _ = False -- Explicit construction (<&>) :: Cool -> Cool -> Cool (<&>) = And (<&) :: Bool -> Cool -> Cool a <& b = Atom a <&> b (&>) :: Cool -> Bool -> Cool a &> b = a <&> Atom b (&) :: Bool -> Bool -> Cool a & b = Atom a <&> Atom b a <||> b = Not (Not a <&> Not b) -- Consumers data Sched = Flip Bool Sched Sched -- | Fixed | Unsched deriving (Show, Eq) -- instance Show Sched where show = drawTree . toTree split :: Sched -> Sched split Unsched = Flip False Unsched Unsched split s = s sched0 = Unsched -- toTree :: Sched -> Tree String -- toTree Unsched = Node "*" [] -- toTree (Flip b s1 s2) = Node (show b) [toTree s1, toTree s2] -- Run the given schedule run :: Sched -> Cool -> Bool run (Unsched) c = toBool c run s@(Flip b s1 s2) c = case c of Atom b -> b Not c -> not (run s c) Seq c1 c2 -> run s1 c1 && run s2 c2 And c1 c2 | b -> run s2 c2 && run s1 c1 | otherwise -> run s1 c1 && run s2 c2 -- Returns a schedule with optimal short-circuiting behaviour lookahead :: Sched -> Cool -> (Sched, Bool) lookahead s c = case c of Atom b -> (s,b) Not c -> fmap not (lookahead s c) Seq c1 c2 -> go (lookahead s1 c1) (lookahead s2 c2) where (Flip b s1 s2) = split s go (s1',r1) ~(s2',r2) = case r1 of True -> case r2 of True -> (Flip False s1' s2', True) -- Set to unflipped if True False -> (Flip True s1 s2', False) False -> (Flip b s1' s2, False) -- (Flip b s1' s2', r1 && r2) -- where (s1', r1) = lookahead s1 c1 -- (s2', r2) = lookahead s2 c2 -- (Flip b s1 s2) = split s And c1 c2 | b -> go (\b' -> flip (Flip b')) (lookahead s2 c2) (lookahead s1 c1) | otherwise -> go (\b' -> Flip b') (lookahead s1 c1) (lookahead s2 c2) where (Flip b s1 s2) = split s go flp (s1',r1) ~(s2',r2) = case r1 of True -> case r2 of True -> (flp False s1' s2', True) -- Set to unflipped if True False -> (flp (not b) s1 s2', False) False -> (flp b s1' s2, False) -- Flip all evaluated parallel conjunctions par :: Sched -> Cool -> (Sched, Bool) par s c = case c of Atom b -> (s,b) Not c -> fmap not (par s c) Seq c1 c2 -- -> (Flip b s1' s2', r1 && r2) | b -> go (\b' -> flip (Flip b')) (par s2 c2) (par s1 c1) | otherwise -> go (\b' -> Flip b') (par s1 c1) (par s2 c2) where (Flip b s1 s2) = split s go flp (s1',r1) ~(s2',r2) = case r1 of True -> case r2 of True -> (flp b s1' s2', True) -- Flip here? False -> (flp b s1 s2', False) False -> (flp b s1' s2, False) And c1 c2 | b -> go (\b' -> flip (Flip b')) (par s2 c2) (par s1 c1) | otherwise -> go (\b' -> Flip b') (par s1 c1) (par s2 c2) where (Flip b s1 s2) = split s go flp (s1',r1) ~(s2',r2) = case r1 of True -> case r2 of True -> (flp (False) s1' s2', True) -- Flip here? False -> (flp (not b) s1 s2', False) False -> (flp (not b) s1' s2, False) -- Returns a schedule with optimal short-circuiting behaviour and -- giving preference to choice-subset operands subsetsc :: IO Int -> Sched -> Cool -> IO (Sched, Bool) subsetsc io s0 c0 = go s0 c0 where go s c = case c of Atom b -> return (s,b) Not c' -> fmap (fmap not) (go s c') Seq c1 c2 -> do (s1', r1) <- go s1 c1 (s2', r2) <- go s2 c2 return (Flip b s1' s2', r1 && r2) where (Flip b s1 s2) = split s And c1 c2 | b -> go' (\b' -> flip (Flip b')) (go s2 c2) (go s1 c1) | otherwise -> go' (\b' -> Flip b') (go s1 c1) (go s2 c2) where -- unchanged s1' s2' | b = Flip (Flip b s1 s2) = split s go' flp m1 m2 = do (s1',r1) <- m1 case r1 of True -> do (s2',r2) <- m2 case r2 of True -> return (flp False s1' s2', True) -- Set to unflipped if True False -> return (flp (not b) s1 s2', False) False -> do n <- io (s2',r2) <- m2 case r2 of True -> return (flp b s1' s2, False) False -> do n' <- io if (n' > n) -- The other operand made at least one distinct choice then return (flp b s1' s2, False) else return (flp (not b) s1 s2', False) -- measure :: IO Int -> Sched -> Cool {- suspending :: IORef (Map ThreadId (ThreadId, ThreadId)) -> IORef (Map ThreadId Integer) -> Cool -> IO Bool suspending threads ref c0 = do mv <- newEmptyMVar forkIO (go (putMVar mv) c0) takeMVar mv where go :: (Bool -> IO ()) -> Cool -> IO () go res c = case c of Not c' -> go (res . not) c' And c1 c2 -> do mv <- newEmptyMVar let res' = putMVar mv t <- myThreadId t1 <- forkIO (go res' c1) t2 <- forkIO (go res' c2) atomicModifyIORef threads (\m -> (M.insert t (t1,t2) m,())) b1 <- takeMVar mv let cleanup = do ts <- fmap (`clean` (t1,t2)) (readIORef threads) mapM killThread ts ret b = cleanup >> res b case b1 of False -> ret False True -> do b2 <- takeMVar mv case b2 of False -> ret False True -> ret True Atom b -> evaluate b >>= res clean :: Map ThreadId (ThreadId, ThreadId) -> (ThreadId, ThreadId) -> [ThreadId] clean m (t1,t2) = t1:t2:(r t1 ++ r t2) where r t = maybe [] id (fmap (clean m) (M.lookup t m)) -} {- runInterl :: Cool -> Bool runInterl = unRes . interl data Res = Now Bool | Later Res unRes :: Res -> Bool unRes (Now b) = b unRes (Later x) = unRes x interl :: Cool -> Res interl (Not c) = Later (interl c) -- Negating consumes an action interl (And c1 c2) = mer (interl c1) (interl c2) where {- mer :: Res -> Res -> Res mer r1 r2 = case r1 of Now False -> Now False Now True -> r2 Later r1' -> Later (mer r2 r1') -} mer :: Res -> Res -> Res mer (Now False) _ = Now False mer _ (Now False) = Now False mer (Now True) (Now True) = Now True mer (Later r1') (Later r2') = Later (mer r1' r2') interl (Atom b) = Now b interl (Seq c1 c2) = seqi (interl c1) (interl c2) where seqi (Now False) r2 = Now False seqi (Now True) r2 = r2 seqi (Later r1') r2 = Later (seqi r1' r2) prune :: Cool -> Cool -> (Bool,Cool) prune (Atom a) c2 = (a, c2) prune (Not c1) ~(Not d1) = case prune c1 d1 of (b,c) -> (not b, Not c) prune (And c1 c2) ~(And d1 d2) = case prune c1 d1 of (False, p) -> (False, p) (True, p) -> case prune c2 d2 of (False, q) -> (False, q) (True, q) -> (True, Seq p q) prune (Seq c1 c2) ~(Seq d1 d2) = case prune c1 d1 of (False, p) -> case prune c2 d2 of (False, q) -> (False, And p q) (True, q) -> (False, p) (True, p) -> case prune c2 d2 of (False, q) -> (False, q) (True, q) -> (True, Seq p q) -- Check that the schedule is optimal rerun :: Sched -> Cool -> Maybe Bool rerun Unsched c = Just (toBool c) -- Should only be Not and Atom rerun s@(Flip b s1 s2) c = case c of Atom b -> Just b Not c -> fmap not (rerun s c) Seq c1 c2 -> rerun s1 c1 && run s2 c2 And c1 c2 | b -> rerun s2 c2 &&&& rerun s1 c1 | otherwise -> rerun s1 c1 &&&& rerun s2 c2 where Just False &&&& _ = Just False Just True &&&& Just True = Just True _ &&&& _ = Nothing -} {- lazify :: Cool -> Cool -> (Bool, Bool) lazify (Atom a) ~(Atom x) = (a,x) lazify (Not a) ~(Not x) = case lazify a x of (b1,b2) -> (not b1, not b2) lazify (And a b) ~(And x y) = case lazify a x of (False, p) -> (False, p) (True, p) -> case lazify b y of (False, q) -> (False, q) (True, q) -> (True, p && q) coolio :: Cool -> Cool -> Cool -> (Bool, Bool, Bool) coolio (Atom a) ~(Atom x) ~(Atom p) = (a,x,p) coolio (And a b) ~(And x y) ~(And p q) = case coolio a x p of (False, rx, rp) -> (False, rx, rp) (True, rx, rp) -> case coolio b y q of (False, ry, rq) -> (False, ry, rq) (True, ry, rq) -> (True, rx && ry, rp && rq) data UnCool = UnCool deriving (Show,Read) instance Exception UnCool unCool :: a -> IO (IO Bool, a) unCool a = do r <- newIORef False let toggle = atomicModifyIORef r (\b -> (not b, b)) a' = unsafeDupablePerformIO $ do b <- readIORef r print b if b then return a else throw UnCool return (toggle, a') -- unCool :: a isCool :: Bool -> Bool isCool b = unsafeDupablePerformIO $ do catch (b `seq` return True) (\UnCool -> return False) test = do (tog,a) <- unCool 1 let x = Just a catch (x == Just 1 `seq` print True) (\UnCool -> print "Cool") tog -- >>= print catch (x == Just 1 `seq` print True) (\UnCool -> print "Not Cool") -} -- uncool