random-fu-0.1.4: src/Data/Random/Source/PureMT.hs
{-# LANGUAGE
CPP,
BangPatterns,
MultiParamTypeClasses,
FlexibleContexts, FlexibleInstances,
UndecidableInstances,
GADTs, RankNTypes,
ScopedTypeVariables
#-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- |This module provides functions useful for implementing new 'MonadRandom'
-- and 'RandomSource' instances for state-abstractions containing 'PureMT'
-- values (the pure pseudorandom generator provided by the
-- mersenne-random-pure64 package), as well as instances for some common
-- cases.
--
-- A 'PureMT' generator is immutable, so 'PureMT' by itself cannot be a
-- 'RandomSource' (if it were, it would always give the same \"random\"
-- values). Some form of mutable state must be used, such as an 'IORef',
-- 'State' monad, etc.. A few default instances are provided by this module
-- along with more-general functions ('getRandomPrimFromMTRef' and
-- 'getRandomPrimFromMTState') usable as implementations for new cases
-- users might need.
module Data.Random.Source.PureMT
( PureMT, newPureMT, pureMT
, module Data.Random.Source.PureMT
) where
import Data.Random.Internal.Primitives
import Data.Random.Source
import System.Random.Mersenne.Pure64
import Data.StateRef
import Control.Monad.State
import qualified Control.Monad.ST.Strict as S
import qualified Control.Monad.State.Strict as S
-- |Given a function for applying a 'PureMT' transformation to some hidden
-- state, this function derives a function able to generate all 'Prim's
-- in the given monad. This is then suitable for either a 'MonadRandom' or
-- 'RandomSource' instance, where the 'supportedPrims' or
-- 'supportedPrimsFrom' function (respectively) is @const True@.
{-# INLINE getRandomPrimBy #-}
getRandomPrimBy :: Monad m => (forall t. (PureMT -> (t, PureMT)) -> m t) -> Prim a -> m a
getRandomPrimBy getThing = getPrimWhere supported (\prim -> getThing (genPrim prim))
where
{-# INLINE supported #-}
supported :: Prim a -> Bool
supported PrimWord64 = True
supported PrimDouble = True
supported _ = False
{-# INLINE genPrim #-}
genPrim :: Prim a -> (PureMT -> (a, PureMT))
genPrim PrimWord64 = randomWord64
genPrim PrimDouble = randomDouble
genPrim p = error ("getRandomPrimBy: genPrim called for unsupported prim " ++ show p)
-- |Given a mutable reference to a 'PureMT' generator, we can implement
-- 'RandomSource' for in any monad in which the reference can be modified.
--
-- Typically this would be used to define a new 'RandomSource' instance for
-- some new reference type or new monad in which an existing reference type
-- can be modified atomically. As an example, the following instance could
-- be used to describe how 'IORef' 'PureMT' can be a 'RandomSource' in the
-- 'IO' monad:
--
-- > instance RandomSource IO (IORef PureMT) where
-- > supportedPrimsFrom _ _ = True
-- > getSupportedRandomPrimFrom = getRandomPrimFromMTRef
--
-- (note that there is actually a more general instance declared already
-- covering this as a a special case, so there's no need to repeat this
-- declaration anywhere)
--
-- Example usage:
--
-- > main = do
-- > src <- newIORef (pureMT 1234) -- OR: newPureMT >>= newIORef
-- > x <- sampleFrom src (uniform 0 100) -- OR: runRVar (uniform 0 100) src
-- > print x
getRandomPrimFromMTRef ::
forall sr m t.
(Monad m, ModifyRef sr m PureMT) => sr -> Prim t -> m t
getRandomPrimFromMTRef ref = getRandomPrimBy getThing
where
{-# INLINE getThing #-}
getThing :: forall a. (PureMT -> (a, PureMT)) -> m a
getThing thing = atomicModifyReference ref $ \(!oldMT) ->
case thing oldMT of (!w, !newMT) -> (newMT, w)
-- |Similarly, @getRandomPrimFromMTState x@ can be used in any \"state\"
-- monad in the mtl sense whose state is a 'PureMT' generator.
-- Additionally, the standard mtl state monads have 'MonadRandom' instances
-- which do precisely that, allowing an easy conversion of 'RVar's and
-- other 'Distribution' instances to \"pure\" random variables (e.g., by
-- @runState . sample :: Distribution d t => d t -> PureMT -> (t, PureMT)@.
-- 'PureMT' in the type there can be replaced by 'StdGen' or anything else
-- satisfying @MonadRandom (State s) => s@).
--
-- For example, this module includes the following declaration:
--
-- > instance MonadRandom (State PureMT) where
-- > supportedPrims _ _ = True
-- > getSupportedRandomPrim = getRandomPrimFromMTState
--
-- This describes a \"standard\" way of getting random values in 'State'
-- 'PureMT', which can then be used in various ways, for example (assuming
-- some 'RVar' @foo@ and some 'Word64' @seed@):
--
-- > runState (runRVar foo StdRandom) (pureMT seed)
-- > runState (sampleFrom StdRandom foo) (pureMT seed)
-- > runState (sample foo) (pureMT seed)
--
-- Of course, the initial 'PureMT' state could also be obtained by any other
-- convenient means, such as 'newPureMT' if you don't care what seed is used.
getRandomPrimFromMTState ::
forall m t.
MonadState PureMT m
=> Prim t -> m t
getRandomPrimFromMTState = getRandomPrimBy getThing
where
{-# INLINE getThing #-}
getThing :: forall a. (PureMT -> (a, PureMT)) -> m a
getThing thing = do
!mt <- get
let (!ws, !newMt) = thing mt
put newMt
return ws
#ifndef MTL2
instance MonadRandom (State PureMT) where
getRandomPrim = getRandomPrimFromMTState
instance MonadRandom (S.State PureMT) where
getRandomPrim = getRandomPrimFromMTState
#endif
instance (Monad m1, ModifyRef (Ref m2 PureMT) m1 PureMT) => RandomSource m1 (Ref m2 PureMT) where
getRandomPrimFrom = getRandomPrimFromMTRef
instance Monad m => MonadRandom (StateT PureMT m) where
getRandomPrim = getRandomPrimFromMTState
instance Monad m => MonadRandom (S.StateT PureMT m) where
getRandomPrim = getRandomPrimFromMTState
instance (Monad m, ModifyRef (IORef PureMT) m PureMT) => RandomSource m (IORef PureMT) where
{-# SPECIALIZE instance RandomSource IO (IORef PureMT) #-}
getRandomPrimFrom = getRandomPrimFromMTRef
instance (Monad m, ModifyRef (STRef s PureMT) m PureMT) => RandomSource m (STRef s PureMT) where
{-# SPECIALIZE instance RandomSource (ST s) (STRef s PureMT) #-}
{-# SPECIALIZE instance RandomSource (S.ST s) (STRef s PureMT) #-}
getRandomPrimFrom = getRandomPrimFromMTRef
-- Note that this instance is probably a Bad Idea. STM allows random variables
-- to interact in spooky quantum-esque ways - One transaction can 'retry' until
-- it gets a \"random\" answer it likes, which causes it to selectively consume
-- entropy, biasing the supply from which other random variables will draw.
-- instance (Monad m, ModifyRef (TVar PureMT) m PureMT) => RandomSource m (TVar PureMT) where
-- {-# SPECIALIZE instance RandomSource IO (TVar PureMT) #-}
-- {-# SPECIALIZE instance RandomSource STM (TVar PureMT) #-}
-- getRandomPrimFrom = getRandomPrimFromMTRef