chp-2.1.0: Control/Concurrent/CHP/Channels/Creation.hs
-- Communicating Haskell Processes.
-- Copyright (c) 2008--2009, University of Kent.
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are
-- met:
--
-- * Redistributions of source code must retain the above copyright
-- notice, this list of conditions and the following disclaimer.
-- * Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
-- * Neither the name of the University of Kent nor the names of its
-- contributors may be used to endorse or promote products derived from
-- this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
-- IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-- THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-- PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-- | This module contains a proliferation of channel creation methods.
--
-- For most uses, 'newChannel' is the only method needed from this module. This
-- creates a channel for you to use. The channel will be automatically destroyed
-- during garbage collection when it falls out of use, so there is no need to do
-- anything to destroy it.
--
-- It is often possible for the type system to infer which channel you want when
-- you use 'newChannel'. If the types of the ends are known by the type system,
-- the channel-type can be inferred. So you can usually just write 'newChannel',
-- and depending on how you use the channel, the type system will figure out
-- which one you needed.
--
-- If this gives a type error along the lines of:
--
-- > Ambiguous type variables `r', `w' in the constraint:
-- > `Channel r w' arising from a use of `newChannel' at tmp.hs:3:24-33
-- > Probable fix: add a type signature that fixes these type variable(s)
--
-- Then you must either explicitly type the channel ends you are using, or more
-- simply, use one of the synonyms in "Control.Concurrent.CHP.Channels.Synonyms"
-- to indicate which kind of channel you are allocating.
--
-- Several other functions in this module, such as 'newChannelWR', 'newChannels'
-- and 'newChannelList' are helpers built with newChannel to ease dealing with
-- channel creation.
--
-- The remainder of the functions in this module are related to traces (see "Control.Concurrent.CHP.Traces"),
-- and allowing the channels to show up usefully in traces: see 'newChannel'' and
-- 'ChanOpts'.
--
-- The channel creation methods were refactored in version 1.5.0. Your code will
-- only be affected if you were using the trace-related methods (for labelling
-- the channels in traces). Instead of using @oneToOneChannelWithLabel "foo"@,
-- you should use @oneToOneChannel' $ chanLabel "foo"@.
module Control.Concurrent.CHP.Channels.Creation (
Chan, Channel(..), newChannel, ChanOpts(..), defaultChanOpts, chanLabel, newChannelWR, newChannelRW, ChannelTuple(..),
newChannelList, newChannelListWithLabels, newChannelListWithStem,
labelChannel
) where
import Control.Monad
import Data.Unique
import Control.Concurrent.CHP.Base
import Control.Concurrent.CHP.Channels.Base
import Control.Concurrent.CHP.Mutex
import Control.Concurrent.CHP.Traces.Base
-- | A class used for allocating new channels, and getting the reading and
-- writing ends. There is a bijective assocation between the channel, and
-- its pair of end types. You can see the types in the list of instances below.
-- Thus, 'newChannel' may be used, and the compiler will infer which type of
-- channel is required based on what end-types you get from 'reader' and 'writer'.
-- Alternatively, if you explicitly type the return of 'newChannel', it will
-- be definite which ends you will use. If you do want to fix the type of
-- the channel you are using when you allocate it, consider using one of the
-- many 'oneToOneChannel'-like shorthand functions that fix the type.
class Channel r w where
-- | Like 'newChannel' but allows you to specify a way to convert the values
-- into Strings in order to display them in the traces, and a label for the traces. If
-- you don't use traces, you can use 'newChannel'.
--
-- Added in version 1.5.0.
newChannel' :: MonadCHP m => ChanOpts a -> m (Chan r w a)
-- | Determines if two channel-ends refer to the same channel.
--
-- This function was added in version 1.4.0.
sameChannel :: r a -> w a -> Bool
-- | Options for channel creation; a function to show the inner data, and an optional
-- label (both only affect tracing). These options can be passed to newChannel'.
--
-- Added in version 1.5.0.
data ChanOpts a = ChanOpts {
chanOptsPriority :: Int,
-- ^ Added in version 2.1.0. Priority is per-event, static and system-wide.
-- If it is possible at any given moment for a process to resolve a choice one
-- of several ways, the channel/barrier with the highest priority is chosen.
-- If the choice is a conjunction and all events in one conjunction are higher
-- than all the events in the other, the higher one is chosen (otherwise no guarantees
-- are made). The default is zero, and the range is the full range of Int (both
-- positive and negative).
chanOptsShow :: a -> String, chanOptsLabel :: Maybe String }
-- | The default: don't show anything, don't label anything
--
-- Added in version 1.5.0.
defaultChanOpts :: ChanOpts a
defaultChanOpts = ChanOpts 0 (const "") Nothing
-- | Uses the Show instance for showing the data in traces, and the given label.
--
-- Added in version 1.5.0.
chanLabel :: Show a => String -> ChanOpts a
chanLabel = ChanOpts 0 show . Just
-- | Allocates a new channel. Nothing need be done to
-- destroy\/de-allocate the channel when it is no longer in use.
--
-- This function does not add any information to the traces: see newChannel' for
-- that purpose.
--
-- In version 1.5.0, this function was moved out of the 'Channel' class, but that
-- should only matter if you were declaring your own instances of that class (very
-- unlikely).
newChannel :: (MonadCHP m, Channel r w) => m (Chan r w a)
newChannel = newChannel' defaultChanOpts
-- | A helper class for easily creating several channels of the same type.
-- The same type refers not only to what type the channel carries, but
-- also to the type of channel (one-to-one no poison, one-to-any with
-- poison, etc). You can write code like this:
--
-- > (a, b, c, d, e) <- newChannels
--
-- To create five channels of the same type.
class ChannelTuple t where
newChannels :: MonadCHP m => m t
-- | A helper that is like 'newChannel' but returns the reading and writing
-- end of the channels directly.
newChannelRW :: (Channel r w, MonadCHP m) => m (r a, w a)
newChannelRW = do c <- newChannel
return (reader c, writer c)
-- | A helper that is like 'newChannel' but returns the writing and reading
-- end of the channels directly.
newChannelWR :: (Channel r w, MonadCHP m) => m (w a, r a)
newChannelWR = do c <- newChannel
return (writer c, reader c)
-- | Creates a list of channels of the same type with the given length. If
-- you need to access some channels by index, use this function. Otherwise
-- you may find using 'newChannels' to be easier.
newChannelList :: (Channel r w, MonadCHP m) => Int -> m [Chan r w a]
newChannelList n = replicateM n newChannel
-- | A helper that is like 'newChannelList', but labels the channels according
-- to a pattern. Given a stem such as foo, it names the channels in the list
-- foo0, foo1, foo2, etc.
newChannelListWithStem :: (Channel r w, MonadCHP m) => Int -> String -> m [Chan r w a]
newChannelListWithStem n s = sequence [newChannel' $ ChanOpts 0 (const "") (Just $ s ++ show i) | i <- [0 .. (n - 1)]]
-- | A helper that is like 'newChannelList', but labels the channels with the
-- given list. The number of channels returned is the same as the length of
-- the list of labels
newChannelListWithLabels :: (Channel r w, MonadCHP m) => [String] -> m [Chan r w a]
newChannelListWithLabels = mapM (newChannel' . ChanOpts 0 (const "") . Just)
instance (Channel r w) => ChannelTuple (Chan r w a, Chan r w a) where
newChannels = do c0 <- newChannel
c1 <- newChannel
return (c0, c1)
instance (Channel r w) => ChannelTuple (Chan r w a, Chan r w a, Chan r w a) where
newChannels = do c0 <- newChannel
c1 <- newChannel
c2 <- newChannel
return (c0, c1, c2)
instance (Channel r w) => ChannelTuple (Chan r w a, Chan r w a, Chan r w a,
Chan r w a) where
newChannels = do c0 <- newChannel
c1 <- newChannel
c2 <- newChannel
c3 <- newChannel
return (c0, c1, c2, c3)
instance (Channel r w) => ChannelTuple (Chan r w a, Chan r w a, Chan r w a,
Chan r w a, Chan r w a) where
newChannels = do c0 <- newChannel
c1 <- newChannel
c2 <- newChannel
c3 <- newChannel
c4 <- newChannel
return (c0, c1, c2, c3, c4)
instance (Channel r w) => ChannelTuple (Chan r w a, Chan r w a, Chan r w a,
Chan r w a, Chan r w a, Chan r w a) where
newChannels = do c0 <- newChannel
c1 <- newChannel
c2 <- newChannel
c3 <- newChannel
c4 <- newChannel
c5 <- newChannel
return (c0, c1, c2, c3, c4, c5)
-- | Labels a channel in the traces. It is easiest to do this at creation.
-- The effect of re-labelling channels after their first use is undefined.
--
-- Added in version 1.5.0.
labelChannel :: MonadCHP m => Chan r w a -> String -> m ()
labelChannel c = liftCHP . liftPoison . liftTrace . labelUnique (getChannelIdentifier c)
instance Channel Chanin Chanout where
newChannel' o = do c <- chan (stmChannel (chanOptsPriority o) (chanOptsShow o)) Chanin Chanout
maybe (return ()) (labelChannel c) (chanOptsLabel o)
return c
sameChannel (Chanin x) (Chanout y) = x == y
instance Channel (Shared Chanin) Chanout where
newChannel' o = do
m <- newMutex
c <- newChannel' o
return $ Chan (getChannelIdentifier c) (Shared (m, reader c)) (writer c)
sameChannel (Shared (_, Chanin x)) (Chanout y) = x == y
instance Channel Chanin (Shared Chanout) where
newChannel' o = do
m <- newMutex
c <- newChannel' o
return $ Chan (getChannelIdentifier c) (reader c) (Shared (m, writer c))
sameChannel (Chanin x) (Shared (_, Chanout y)) = x == y
instance Channel (Shared Chanin) (Shared Chanout) where
newChannel' o = do
m <- newMutex
m' <- newMutex
c <- newChannel' o
return $ Chan (getChannelIdentifier c) (Shared (m, reader c)) (Shared (m', writer c))
sameChannel (Shared (_, Chanin x)) (Shared (_, Chanout y)) = x == y
chan :: Monad m => m (Unique, c a) -> (c a -> r a) -> (c a -> w a) -> m (Chan r w a)
chan m r w = do (u, x) <- m
return $ Chan u (r x) (w x)