{- - ``Data/Random/Source'' -} {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, GADTs #-} module Data.Random.Source ( MonadRandom(..) , RandomSource(..) , Prim(..) ) where import Data.Word import Control.Monad.Prompt import Data.Tagged import Data.Random.Internal.Primitives -- |A typeclass for monads with a chosen source of entropy. For example, -- 'RVar' is such a monad - the source from which it is (eventually) sampled -- is the only source from which a random variable is permitted to draw, so -- when directly requesting entropy for a random variable these functions -- are used. -- -- The minimal definition is 'supportedPrims' and 'getSupportedRandomPrim' -- with cases for those primitives where 'supportedPrims' returns 'True'. -- -- It is recommended (despite the warnings it generates) that, even when -- all primitives are supported, a final wildcard case of 'supportedPrims' is -- specified, as: -- -- > supportedPrims _ _ = False -- -- The overlapping pattern warnings can be suppressed (without suppressing -- other, genuine, overlapping-pattern warnings) by the GHC flag -- @-fno-warn-simple-patterns@. This is not actually the documented behavior -- of that flag as far as I can find in 3 google-minutes, but it works with -- GHC 6.12.1 anyway, and that's good enough for me. -- -- Note that it is very important that at least 'supportedPrims' (and preferably -- 'getSupportedRandomPrim' as well) gets inlined into the default implementation -- of 'getRandomPrim'. If your 'supportedPrims' is more than about 2 or 3 -- cases, add an INLINE pragma so that it can be optimized out of 'getRandomPrim'. class Monad m => MonadRandom m where -- |Predicate indicating whether a given primitive is supported by the -- instance. The first parameter is a phantom used to select the instance. supportedPrims :: m () -> Prim t -> Bool -- |Generate a random value corresponding to the specified primitive. Will -- not be called unless supportedPrims returns true for that primitive. getSupportedRandomPrim :: Prim t -> m t -- This could just be a function, but placing it in a dictionary gives -- GHC a place to optimize it separately for each instance, which is -- kinda the whole point of the 'Prim' machinery: -- -- |Generate a random value corresponding to the specified primitive. The -- default implementation makes use of 'supportedPrims' and 'getSupportedRandomPrim' -- to construct any required Prim out of the supported ones. {-# NOINLINE getRandomPrim #-} getRandomPrim :: Prim t -> m t getRandomPrim prim = val where val = runPromptM getSupportedRandomPrim (decomposePrimWhere (supportedPrims mPhantom) prim) mPhantom = error "supportedPrims tried to evaluate a phantom parameter" `asTypeOf` (val >> return ()) -- |A source of entropy which can be used in the given monad. -- -- The minimal definition is 'supportedPrimsFrom' and 'getSupportedRandomPrimFrom' -- with cases for those primitives where 'supportedPrimsFrom' returns 'True'. -- -- Note that it is very important that at least 'supportedPrimsFrom' (and preferably -- 'getSupportedRandomPrimFrom' as well) gets inlined into the default implementation -- of 'getRandomPrimFrom'. If your 'supportedPrimsFrom' is more than about 2 or 3 -- cases, add an INLINE pragma so that it can be optimized out of 'getRandomPrimFrom'. -- -- See also 'MonadRandom'. class Monad m => RandomSource m s where -- |Predicate indicating whether a given primitive is supported by the -- instance. The tag on the first parameter is a phantom used only to -- select the instance, but the value itself may be inspected. supportedPrimsFrom :: Tagged (m ()) s -> Prim t -> Bool -- |Generate a random value corresponding to the specified primitive getSupportedRandomPrimFrom :: s -> Prim t -> m t -- This could just be a function, but placing it in a dictionary gives -- GHC a place to optimize it separately for each instance, which is -- kinda the whole point of the 'Prim' machinery: -- -- |Generate a random value corresponding to the specified primitive. The -- default implementation makes use of 'supportedPrimsFrom' and -- 'getSupportedRandomPrimFrom' to construct any required Prim out of -- the supported ones. {-# NOINLINE getRandomPrimFrom #-} getRandomPrimFrom :: s -> Prim t -> m t getRandomPrimFrom src prim = val where val = runPromptM (getSupportedRandomPrimFrom src) (decomposePrimWhere supported prim) supported :: Prim t -> Bool supported = supportedPrimsFrom (tagIt (val >> return ()) src) tagIt :: a -> b -> Tagged a b tagIt _ it = Tagged it instance Monad m => RandomSource m (m Word8) where supportedPrimsFrom _ PrimWord8 = True supportedPrimsFrom _ _ = False getSupportedRandomPrimFrom f PrimWord8 = f getSupportedRandomPrimFrom _ p = error ("getSupportedRandomPrimFrom/RandomSource m (m Word8): unsupported prim requested: " ++ show p) instance Monad m => RandomSource m (m Word16) where supportedPrimsFrom _ PrimWord16 = True supportedPrimsFrom _ _ = False getSupportedRandomPrimFrom f PrimWord16 = f getSupportedRandomPrimFrom _ p = error ("getSupportedRandomPrimFrom/RandomSource m (m Word16): unsupported prim requested: " ++ show p) instance Monad m => RandomSource m (m Word32) where supportedPrimsFrom _ PrimWord32 = True supportedPrimsFrom _ _ = False getSupportedRandomPrimFrom f PrimWord32 = f getSupportedRandomPrimFrom _ p = error ("getSupportedRandomPrimFrom/RandomSource m (m Word32): unsupported prim requested: " ++ show p) instance Monad m => RandomSource m (m Word64) where supportedPrimsFrom _ PrimWord64 = True supportedPrimsFrom _ _ = False getSupportedRandomPrimFrom f PrimWord64 = f getSupportedRandomPrimFrom _ p = error ("getSupportedRandomPrimFrom/RandomSource m (m Word64): unsupported prim requested: " ++ show p) instance Monad m => RandomSource m (m Double) where supportedPrimsFrom _ PrimDouble = True supportedPrimsFrom _ _ = False getSupportedRandomPrimFrom f PrimDouble = f getSupportedRandomPrimFrom _ p = error ("getSupportedRandomPrimFrom/RandomSource m (m Double): unsupported prim requested: " ++ show p)