{-# LANGUAGE MultiParamTypeClasses, DeriveDataTypeable #-} module Control.Concurrent.Chan.Split ( -- * Chan pairs InChan() , OutChan() -- * Utility functions: , getChanContents , dupChan -- * Supporting module , module Control.Concurrent.Chan.Class ) where -- -- inspired by Leon P. Smith's from-scratch implementation -- copy-pasta from Control.Concurrent.Chan -- import System.IO.Unsafe ( unsafeInterleaveIO ) import Control.Concurrent.MVar import Control.Exception (mask_) import Data.Typeable import Control.Concurrent.Chan.Class type Stream a = MVar (ChItem a) data ChItem a = ChItem a (Stream a) -- | The \"write side\" of a chan pair newtype InChan i = InChan (MVar (Stream i)) -- Invariant: Stream i always empty deriving (Eq, Typeable) -- | The \"read side\" of a chan pair newtype OutChan i = OutChan (MVar (Stream i)) deriving (Eq, Typeable) instance NewSplitChan InChan OutChan where -- | Create corresponding read and write ends of a chan pair. Writes to the -- 'InChan' side can be read on the 'OutChan' side. newSplitChan = do hole <- newEmptyMVar readVar <- newMVar hole writeVar <- newMVar hole return ( InChan writeVar, OutChan readVar ) instance SplitChan InChan OutChan where writeChan (InChan writeVar) val = do new_hole <- newEmptyMVar mask_ $ do old_hole <- takeMVar writeVar putMVar old_hole (ChItem val new_hole) putMVar writeVar new_hole writeList2Chan ch ls = sequence_ (map (writeChan ch) ls) readChan (OutChan readVar) = do modifyMVar readVar $ \read_end -> do (ChItem val new_read_end) <- readMVar read_end -- Use readMVar here, not takeMVar, -- else dupChan doesn't work return (new_read_end, val) -- | Return a lazy list representing the contents of the supplied OutChan, much -- like System.IO.hGetContents. getChanContents :: OutChan a -> IO [a] getChanContents ch = unsafeInterleaveIO (do x <- readChan ch xs <- getChanContents ch return (x:xs) ) -- | Duplicate an 'OutChan': the duplicate channel contains any unread messages -- in the original (n.b. this differs from the behavior of dupChan in Chan), -- and data written to the corresponding 'InChan' will appear in both, i.e. -- consuming a value from the copy will have no affect on the values in the -- original OutChan. -- -- (Note that a duplicated channel is not equal to its original. -- So: @fmap (c /=) $ dupChan c@ returns @True@ for all @c@.) dupChan :: OutChan a -> IO (OutChan a) dupChan (OutChan writeVar) = OutChan `fmap` withMVar writeVar newMVar