rvar 0.2.0.6 → 0.3.0.0
raw patch · 4 files changed
+205/−88 lines, 4 filesdep +bytestringdep +randomdep −random-sourcedep ~basedep ~mtl
Dependencies added: bytestring, random
Dependencies removed: random-source
Dependency ranges changed: base, mtl
Files
- changelog.md +15/−0
- rvar.cabal +10/−10
- src/Data/RVar.hs +143/−78
- src/Data/RVar/Prim.hs +37/−0
+ changelog.md view
@@ -0,0 +1,15 @@+* Changes in 0.3.0.0:++ * Drop usage of `random-source` in favor of `random`+ * Add `Prim` type that resembles one from `random-source`+ * Add `RGen` type that serves the same purpose as `StdRandom` in `random-source`++* Changes in 0.2.0.6: None. (Pacify Hackage.)++* Changes in 0.2.0.4: Update for GHC 8.8.++* Changes in 0.2.0.3: Version bump for transformers dependency.++* Changes in 0.2.0.2: Version bump for transformers dependency.++* Changes in 0.2.0.1: Version bump for transformers dependency.
rvar.cabal view
@@ -1,12 +1,12 @@ name: rvar-version: 0.2.0.6+version: 0.3.0.0 stability: stable cabal-version: >= 1.10 build-type: Simple author: James Cook <mokus@deepbondi.net>-maintainer: James Cook <mokus@deepbondi.net>+maintainer: Dominic Steinitz <dominic@steinitz.org> license: PublicDomain homepage: https://github.com/mokus0/random-fu @@ -26,16 +26,14 @@ comparable to other Haskell libraries, but still a fair bit slower than straight C implementations of the same algorithms.- .- Changes in 0.2.0.1: Version bump for transformers- dependency. -tested-with: GHC == 6.8.3, GHC == 6.10.4, GHC == 6.12.3,- GHC == 7.0.4, GHC == 7.2.1, GHC == 7.2.2+tested-with: GHC == 8.10.7 +extra-source-files: changelog.md+ source-repository head type: git- location: https://github.com/mokus0/random-fu.git+ location: https://github.com/haskell-numerics/random-fu subdir: rvar Flag mtl2@@ -46,6 +44,7 @@ hs-source-dirs: src default-language: Haskell2010 exposed-modules: Data.RVar+ other-modules: Data.RVar.Prim if flag(mtl2) build-depends: mtl == 2.*@@ -54,6 +53,7 @@ build-depends: mtl == 1.1.* build-depends: base >= 3 && <5,+ bytestring, MonadPrompt == 1.0.*,- random-source == 0.3.*,- transformers >= 0.2 && < 0.6+ transformers >= 0.2 && < 0.6,+ random >= 1.2.0
src/Data/RVar.hs view
@@ -1,112 +1,121 @@ {- - ``Data/Random/RVar'' -}-{-# LANGUAGE- RankNTypes,- MultiParamTypeClasses,- FlexibleInstances, - GADTs,- ScopedTypeVariables,- CPP- #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-} -- |Random variables. An 'RVar' is a sampleable random variable. Because -- probability distributions form a monad, they are quite easy to work with -- in the standard Haskell monadic styles. For examples, see the source for -- any of the 'Distribution' instances - they all are defined in terms of -- 'RVar's.+{-# LANGUAGE FlexibleContexts #-}+ module Data.RVar- ( RandomSource- , MonadRandom- ( getRandomWord8- , getRandomWord16- , getRandomWord32- , getRandomWord64- , getRandomDouble- , getRandomNByteInteger- )- - , RVar- , runRVar, sampleRVar- + ( RVar+ , runRVar, sampleReaderRVar, sampleStateRVar+ , pureRVar+ , RVarT- , runRVarT, sampleRVarT- , runRVarTWith, sampleRVarTWith- ) where+ , runRVarT, sampleReaderRVarT, sampleStateRVarT+ , runRVarTWith, sampleReaderRVarTWith, sampleStateRVarTWith + , RGen(..)+ , uniformRVarT+ , uniformRangeRVarT -import Data.Random.Internal.Source (Prim(..), MonadRandom(..), RandomSource(..))-import Data.Random.Source ({-instances-})+ , Prim(..)+ ) where -import qualified Control.Monad.Trans.Class as T-import Control.Monad (liftM, ap)-import Control.Monad.Prompt (MonadPrompt(..), PromptT, runPromptT)+ import qualified Control.Monad.IO.Class as T-import qualified Control.Monad.Trans as MTL+import Control.Monad.Prompt (MonadPrompt(..), PromptT, runPromptT)+import Control.Monad.Reader as MTL+import Control.Monad.State as MTL+import qualified Control.Monad.Trans.Class as T import qualified Data.Functor.Identity as T+import Data.RVar.Prim+import System.Random.Stateful --- |An opaque type modeling a \"random variable\" - a value --- which depends on the outcome of some random event. 'RVar's +-- |An opaque type modeling a \"random variable\" - a value+-- which depends on the outcome of some random event. 'RVar's -- can be conveniently defined by an imperative-looking style:--- +-- -- > normalPair = do -- > u <- stdUniform -- > t <- stdUniform -- > let r = sqrt (-2 * log u) -- > theta = (2 * pi) * t--- > +-- > -- > x = r * cos theta -- > y = r * sin theta -- > return (x,y)--- +-- -- OR by a more applicative style:--- +-- -- > logNormal = exp <$> stdNormal -- -- Once defined (in any style), there are several ways to sample 'RVar's:--- --- * In a monad, using a 'RandomSource':--- --- > runRVar (uniform 1 100) DevRandom :: IO Int--- --- * In a monad, using a 'MonadRandom' instance: ----- > sampleRVar (uniform 1 100) :: State PureMT Int--- --- * As a pure function transforming a functional RNG:--- --- > sampleState (uniform 1 100) :: StdGen -> (Int, StdGen)+-- * Using an immutable pseudo-random number generator that has an instance for `RandomGen` with+-- `StateT` monad: ----- (where @sampleState = runState . sampleRVar@)+-- >>> import qualified Data.Random as Fu (uniform)+-- >>> import System.Random (mkStdGen)+-- >>> import Control.Monad.State (runState)+-- >>> runState (sampleStateRVar (Fu.uniform 1 (100 :: Integer))) (mkStdGen 2021)+-- (79,StdGen {unStdGen = SMGen 4687568268719557181 4805600293067301895})+--+-- * Using a mutable pseud-random number generator that has an instance for `StatefulGen` with+-- `ReaderT` monad.+--+-- >>> import qualified Data.Random as Fu (uniform)+-- >>> import System.Random.MWC (create)+-- >>> import Control.Monad.Reader (runReaderT)+-- >>> import qualified Data.Vector.Storable as VS+-- >>> initialize (VS.singleton 2021) >>= runReaderT (sampleReaderRVar (uniform 1 (100 :: Integer)))+-- 8+-- type RVar = RVarT T.Identity +-- | Sample random variable using `RandomGen` generator as source of entropy+pureRVar :: RandomGen g => RVar a -> g -> (a, g)+pureRVar rvar g = runStateGen g (runRVar rvar)+ -- |\"Run\" an 'RVar' - samples the random variable from the provided -- source of entropy.-runRVar :: RandomSource m s => RVar a -> s -> m a+runRVar :: StatefulGen g m => RVar a -> g -> m a runRVar = runRVarTWith (return . T.runIdentity) -- |@sampleRVar x@ is equivalent to @runRVar x 'StdRandom'@.-sampleRVar :: MonadRandom m => RVar a -> m a-sampleRVar = sampleRVarTWith (return . T.runIdentity)+sampleReaderRVar :: (StatefulGen g m, MonadReader g m) => RVar a -> m a+sampleReaderRVar = sampleReaderRVarTWith (return . T.runIdentity) +sampleStateRVar :: (RandomGen g, MonadState g m) => RVar a -> m a+sampleStateRVar = sampleStateRVarTWith (return . T.runIdentity)+ -- |A random variable with access to operations in an underlying monad. Useful -- examples include any form of state for implementing random processes with hysteresis, -- or writer monads for implementing tracing of complicated algorithms.--- +-- -- For example, a simple random walk can be implemented as an 'RVarT' 'IO' value: -- -- > rwalkIO :: IO (RVarT IO Double) -- > rwalkIO d = do -- > lastVal <- newIORef 0--- > +-- > -- > let x = do -- > prev <- lift (readIORef lastVal) -- > change <- rvarT StdNormal--- > +-- > -- > let new = prev + change -- > lift (writeIORef lastVal new) -- > return new--- > +-- > -- > return x -- -- To run the random walk it must first be initialized, after which it can be sampled as usual:@@ -119,35 +128,39 @@ -- -- The same random-walk process as above can be implemented using MTL types -- as follows (using @import Control.Monad.Trans as MTL@):--- +-- -- > rwalkState :: RVarT (State Double) Double -- > rwalkState = do -- > prev <- MTL.lift get -- > change <- rvarT StdNormal--- > +-- > -- > let new = prev + change -- > MTL.lift (put new) -- > return new--- +-- -- Invocation is straightforward (although a bit noisy) if you're used to MTL:--- +-- -- > rwalk :: Int -> Double -> StdGen -> ([Double], StdGen)--- > rwalk count start gen = +-- > rwalk count start gen = -- > flip evalState start . -- > flip runStateT gen . -- > sampleRVarTWith MTL.lift $ -- > replicateM count rwalkState newtype RVarT m a = RVarT { unRVarT :: PromptT Prim m a } -runRVarT :: RandomSource m s => RVarT m a -> s -> m a+runRVarT :: StatefulGen g m => RVarT m a -> g -> m a runRVarT = runRVarTWith id -sampleRVarT :: MonadRandom m => RVarT m a -> m a-sampleRVarT = sampleRVarTWith id +sampleStateRVarT :: (RandomGen g, MonadState g m) => RVarT m a -> m a+sampleStateRVarT rvar = runRVarT rvar StateGenM++sampleReaderRVarT :: (StatefulGen g m, MonadReader g m) => RVarT m a -> m a+sampleReaderRVarT rvar = ask >>= runRVarT rvar+ -- | \"Runs\" an 'RVarT', sampling the random variable it defines.--- --- The first argument lifts the base monad into the sampling monad. This +--+-- The first argument lifts the base monad into the sampling monad. This -- operation must obey the \"monad transformer\" laws: -- -- > lift . return = return@@ -166,30 +179,62 @@ -- -- The ability to lift is very important - without it, every 'RVar' would have -- to either be given access to the full capability of the monad in which it--- will eventually be sampled (which, incidentally, would also have to be +-- will eventually be sampled (which, incidentally, would also have to be -- monomorphic so you couldn't sample one 'RVar' in more than one monad)--- or functions manipulating 'RVar's would have to use higher-ranked +-- or functions manipulating 'RVar's would have to use higher-ranked -- types to enforce the same kind of isolation and polymorphism. {-# INLINE runRVarTWith #-}-runRVarTWith :: forall m n s a. RandomSource m s => (forall t. n t -> m t) -> RVarT n a -> s -> m a-runRVarTWith liftN (RVarT m) src = runPromptT return bindP bindN m+runRVarTWith :: forall m n g a. StatefulGen g m => (forall t. n t -> m t) -> RVarT n a -> g -> m a+runRVarTWith liftN (RVarT m) gen = runPromptT return bindP bindN m where bindP :: forall t. (Prim t -> (t -> m a) -> m a)- bindP prim cont = getRandomPrimFrom src prim >>= cont- + bindP prim cont = uniformPrimM prim gen >>= cont+ bindN :: forall t. n t -> (t -> m a) -> m a bindN nExp cont = liftN nExp >>= cont +{-# INLINE uniformPrimM #-}+uniformPrimM :: StatefulGen g m => Prim t -> g -> m t+uniformPrimM prim g =+ case prim of+ PrimWord8 -> uniformWord8 g+ PrimWord16 -> uniformWord16 g+ PrimWord32 -> uniformWord32 g+ PrimWord64 -> uniformWord64 g+ PrimShortByteString n -> uniformShortByteString n g++ -- |@sampleRVarTWith lift x@ is equivalent to @runRVarTWith lift x 'StdRandom'@.-sampleRVarTWith :: forall m n a. MonadRandom m => (forall t. n t -> m t) -> RVarT n a -> m a-sampleRVarTWith liftN (RVarT m) = runPromptT return bindP bindN m+{-# INLINE sampleReaderRVarTWith #-}+sampleReaderRVarTWith ::+ forall m n a g. (StatefulGen g m, MonadReader g m)+ => (forall t. n t -> m t)+ -> RVarT n a+ -> m a+sampleReaderRVarTWith liftN (RVarT m) = runPromptT return bindP bindN m where bindP :: forall t. (Prim t -> (t -> m a) -> m a)- bindP prim cont = getRandomPrim prim >>= cont- + bindP prim cont = ask >>= uniformPrimM prim >>= cont+ bindN :: forall t. n t -> (t -> m a) -> m a bindN nExp cont = liftN nExp >>= cont ++-- |@sampleRVarTWith lift x@ is equivalent to @runRVarTWith lift x 'StdRandom'@.+{-# INLINE sampleStateRVarTWith #-}+sampleStateRVarTWith ::+ forall m n a g. (RandomGen g, MonadState g m)+ => (forall t. n t -> m t)+ -> RVarT n a+ -> m a+sampleStateRVarTWith liftN (RVarT m) = runPromptT return bindP bindN m+ where+ bindP :: forall t. (Prim t -> (t -> m a) -> m a)+ bindP prim cont = uniformPrimM prim StateGenM >>= cont++ bindN :: forall t. n t -> (t -> m a) -> m a+ bindN nExp cont = liftN nExp >>= cont+ instance Functor (RVarT n) where fmap = liftM @@ -197,9 +242,6 @@ return x = RVarT (return $! x) (RVarT m) >>= k = RVarT (m >>= \x -> x `seq` unRVarT (k x)) -instance MonadRandom (RVarT n) where- getRandomPrim = RVarT . prompt- instance Applicative (RVarT n) where pure = return (<*>) = ap@@ -222,3 +264,26 @@ liftIO = MTL.lift . MTL.liftIO #endif++data RGen = RGen++instance StatefulGen RGen (RVarT m) where+ uniformWord8 RGen = RVarT $ prompt PrimWord8+ {-# INLINE uniformWord8 #-}+ uniformWord16 RGen = RVarT $ prompt PrimWord16+ {-# INLINE uniformWord16 #-}+ uniformWord32 RGen = RVarT $ prompt PrimWord32+ {-# INLINE uniformWord32 #-}+ uniformWord64 RGen = RVarT $ prompt PrimWord64+ {-# INLINE uniformWord64 #-}+ uniformShortByteString n RGen = RVarT $ prompt (PrimShortByteString n)+ {-# INLINE uniformShortByteString #-}+++uniformRVarT :: Uniform a => RVarT m a+uniformRVarT = uniformM RGen+{-# INLINE uniformRVarT #-}++uniformRangeRVarT :: UniformRange a => (a, a) -> RVarT m a+uniformRangeRVarT r = uniformRM r RGen+{-# INLINE uniformRangeRVarT #-}
+ src/Data/RVar/Prim.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE RankNTypes #-}+-- |This is an internal interface to support the 'RVar' abstraction. It+-- reifies the operations provided by `System.Random.Stateful.StatefulGen` in a+-- uniform and efficient way, as functions of type @Prim a -> m a@.+module Data.RVar.Prim (Prim(..)) where++import Data.Typeable+import Data.Word+import Data.ByteString.Short++-- |A 'Prompt' GADT describing a request for a primitive random variate. Random variable+-- definitions will request their entropy via these prompts, and entropy sources will+-- satisfy those requests. This data type is needed for creating+-- `System.Random.Stateful.StatefulGen` instance for `Data.RVar.RVarT`+--+data Prim a where+ -- | An unsigned byte, uniformly distributed from 0 to 0xff+ PrimWord8 :: Prim Word8+ -- | An unsigned 16-bit word, uniformly distributed from 0 to 0xffff+ PrimWord16 :: Prim Word16+ -- | An unsigned 32-bit word, uniformly distributed from 0 to 0xffffffff+ PrimWord32 :: Prim Word32+ -- | An unsigned 64-bit word, uniformly distributed from 0 to 0xffffffffffffffff+ PrimWord64 :: Prim Word64+ -- | A uniformly distributed `ShortByteString` of length @n@ bytes+ PrimShortByteString :: !Int -> Prim ShortByteString+ deriving (Typeable)++instance Show (Prim a) where+ showsPrec _p PrimWord8 = showString "PrimWord8"+ showsPrec _p PrimWord16 = showString "PrimWord16"+ showsPrec _p PrimWord32 = showString "PrimWord32"+ showsPrec _p PrimWord64 = showString "PrimWord64"+ showsPrec p (PrimShortByteString n) =+ showParen (p > 10) (showString "PrimShortByteString " . showsPrec 11 n)