conceit-0.1.1.0: src/Control/Concurrent/Conceit.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFunctor #-}
module Control.Concurrent.Conceit (
Conceit (..)
, _Conceit
, conceit
, mapConceit
) where
import Data.Bifunctor
import Data.Monoid
import Data.Typeable
import Data.Traversable
import Control.Applicative
import Control.Monad
import Control.Exception
import Control.Concurrent
import Control.Concurrent.Async
data WrappedError e = WrappedError e
deriving (Show, Typeable)
instance (Show e, Typeable e) => Exception (WrappedError e)
elideError :: (Show e, Typeable e) => IO (Either e a) -> IO a
elideError action = action >>= either (throwIO . WrappedError) return
revealError :: (Show e, Typeable e) => IO a -> IO (Either e a)
revealError action = catch (action >>= return . Right)
(\(WrappedError e) -> return . Left $ e)
{-|
'Conceit' is very similar to 'Control.Concurrent.Async.Concurrently' from the
@async@ package, but it has an explicit error type @e@.
The 'Applicative' instance is used to run actions concurrently, wait until
they finish, and combine their results.
However, if any of the actions fails with @e@ the other actions are
immediately cancelled and the whole computation fails with @e@.
To put it another way: 'Conceit' behaves like 'Concurrently' for successes and
like 'race' for errors.
-}
newtype Conceit e a = Conceit { runConceit :: IO (Either e a) } deriving Functor
instance Bifunctor Conceit where
bimap f g (Conceit x) = Conceit $ liftM (bimap f g) x
instance (Show e, Typeable e) => Applicative (Conceit e) where
pure = Conceit . pure . pure
Conceit fs <*> Conceit as =
Conceit . revealError $
uncurry ($) <$> concurrently (elideError fs) (elideError as)
instance (Show e, Typeable e) => Alternative (Conceit e) where
empty = Conceit $ forever (threadDelay maxBound)
Conceit as <|> Conceit bs =
Conceit $ either id id <$> race as bs
instance (Show e, Typeable e, Monoid a) => Monoid (Conceit e a) where
mempty = Conceit . pure . pure $ mempty
mappend c1 c2 = (<>) <$> c1 <*> c2
_Conceit :: IO a -> Conceit e a
_Conceit = Conceit . fmap pure
conceit :: (Show e, Typeable e)
=> IO (Either e a)
-> IO (Either e b)
-> IO (Either e (a,b))
conceit c1 c2 = runConceit $ (,) <$> Conceit c1 <*> Conceit c2
{-|
Works similarly to 'Control.Concurrent.Async.mapConcurrently' from the
@async@ package, but if any of the computations fails with @e@, the others are
immediately cancelled and the whole computation fails with @e@.
-}
mapConceit :: (Show e, Typeable e, Traversable t) => (a -> IO (Either e b)) -> t a -> IO (Either e (t b))
mapConceit f = revealError . mapConcurrently (elideError . f)