From 1bc9fb162612fabb22de40cb7540bfa73e22f1d5 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Wed, 12 Feb 2020 04:48:15 +0300 Subject: [PATCH 001/170] Allow RNGs to provide efficient implementations for variety of prim types Introduce PrimMonad interface. Add range generation used in splitmix Export all class contents Initial stab at MonadRandom Working MonadRandom Rename next* to gen* --- System/Random.hs | 505 +++++++++++++++++++++++++++++++++++++---------- random.cabal | 6 +- 2 files changed, 400 insertions(+), 111 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index ab7727405..ae196720a 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1,3 +1,7 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE TypeFamilies #-} #if __GLASGOW_HASKELL__ >= 701 {-# LANGUAGE Trustworthy #-} #endif @@ -39,45 +43,49 @@ #include "MachDeps.h" -module System.Random - ( +module System.Random where +-- ( - -- $intro +-- -- $intro - -- * Random number generators +-- -- * Random number generators -#ifdef ENABLE_SPLITTABLEGEN - RandomGen(next, genRange) - , SplittableGen(split) -#else - RandomGen(next, genRange, split) -#endif - -- ** Standard random number generators - , StdGen - , mkStdGen +-- #ifdef ENABLE_SPLITTABLEGEN +-- RandomGen(..) +-- , SplittableGen(..) +-- #else +-- RandomGen(..) +-- #endif +-- , RandomPrimGen(..) +-- -- ** Standard random number generators +-- , StdGen +-- , mkStdGen - -- ** The global random number generator +-- -- ** The global random number generator - -- $globalrng +-- -- $globalrng - , getStdRandom - , getStdGen - , setStdGen - , newStdGen +-- , getStdRandom +-- , getStdGen +-- , setStdGen +-- , newStdGen - -- * Random values of various types - , Random ( random, randomR, - randoms, randomRs, - randomIO, randomRIO ) +-- -- * Random values of various types +-- , Random(..) - -- * References - -- $references +-- -- * References +-- -- $references - ) where +-- ) where import Prelude +import Control.Arrow +import Control.Monad.ST +import Control.Monad.Primitive +import Control.Monad.State.Strict import Data.Bits +import Data.Functor.Identity import Data.Int import Data.Word import Foreign.C.Types @@ -144,46 +152,135 @@ getTime = do #endif class RandomGen g where + type GenSeed g :: * + type GenSeed g = Word64 + + mkGen :: GenSeed g -> g + + saveGen :: g -> GenSeed g + + -- |The 'next' operation returns an 'Int' that is uniformly distributed + -- in the range returned by 'genRange' (including both end points), + -- and a new generator. + next :: g -> (Int, g) + -- `next` can be deprecated over time + + genWord8 :: g -> (Word8, g) + genWord8 = first fromIntegral . genWord32R (fromIntegral (maxBound :: Word8)) - -- |The 'next' operation returns an 'Int' that is uniformly distributed - -- in the range returned by 'genRange' (including both end points), - -- and a new generator. - next :: g -> (Int, g) - - -- |The 'genRange' operation yields the range of values returned by - -- the generator. - -- - -- It is required that: - -- - -- * If @(a,b) = 'genRange' g@, then @a < b@. - -- - -- * 'genRange' always returns a pair of defined 'Int's. - -- - -- The second condition ensures that 'genRange' cannot examine its - -- argument, and hence the value it returns can be determined only by the - -- instance of 'RandomGen'. That in turn allows an implementation to make - -- a single call to 'genRange' to establish a generator's range, without - -- being concerned that the generator returned by (say) 'next' might have - -- a different range to the generator passed to 'next'. - -- - -- The default definition spans the full range of 'Int'. - genRange :: g -> (Int,Int) - - -- default method - genRange _ = (minBound, maxBound) + genWord16 :: g -> (Word16, g) + genWord16 = first fromIntegral . genWord32R (fromIntegral (maxBound :: Word16)) + + genWord32 :: g -> (Word32, g) + genWord32 = genWord32R maxBound + + genWord64 :: g -> (Word64, g) + genWord64 = genWord64R maxBound + + genWord32R :: Word32 -> g -> (Word32, g) + genWord32R m = randomIvalIntegral (minBound, m) + + genWord64R :: Word64 -> g -> (Word64, g) + genWord64R m = randomIvalIntegral (minBound, m) + + + -- |The 'genRange' operation yields the range of values returned by + -- the generator. + -- + -- It is required that: + -- + -- * If @(a,b) = 'genRange' g@, then @a < b@. + -- + -- * 'genRange' always returns a pair of defined 'Int's. + -- + -- The second condition ensures that 'genRange' cannot examine its + -- argument, and hence the value it returns can be determined only by the + -- instance of 'RandomGen'. That in turn allows an implementation to make + -- a single call to 'genRange' to establish a generator's range, without + -- being concerned that the generator returned by (say) 'next' might have + -- a different range to the generator passed to 'next'. + -- + -- The default definition spans the full range of 'Int'. + genRange :: g -> (Int,Int) + + -- default method + genRange _ = (minBound, maxBound) #ifdef ENABLE_SPLITTABLEGEN -- | The class 'SplittableGen' proivides a way to specify a random number -- generator that can be split into two new generators. class SplittableGen g where #endif - -- |The 'split' operation allows one to obtain two distinct random number - -- generators. This is very useful in functional programs (for example, when - -- passing a random number generator down to recursive calls), but very - -- little work has been done on statistically robust implementations of - -- 'split' (["System.Random\#Burton", "System.Random\#Hellekalek"] - -- are the only examples we know of). - split :: g -> (g, g) + -- |The 'split' operation allows one to obtain two distinct random number + -- generators. This is very useful in functional programs (for example, when + -- passing a random number generator down to recursive calls), but very + -- little work has been done on statistically robust implementations of + -- 'split' (["System.Random\#Burton", "System.Random\#Hellekalek"] + -- are the only examples we know of). + split :: g -> (g, g) + + +class Monad m => MonadRandom g m where + type Seed g :: * + + restore :: Seed g -> m g + save :: g -> m (Seed g) + -- | Generate `Word32` up to and including the supplied max value + uniformWord32R :: Word32 -> g -> m Word32 + -- | Generate `Word64` up to and including the supplied max value + uniformWord64R :: Word64 -> g -> m Word64 + + uniformWord8 :: g -> m Word8 + uniformWord8 = fmap fromIntegral . uniformWord32R (fromIntegral (maxBound :: Word8)) + uniformWord16 :: g -> m Word16 + uniformWord16 = fmap fromIntegral . uniformWord32R (fromIntegral (maxBound :: Word16)) + uniformWord32 :: g -> m Word32 + uniformWord32 = uniformWord32R maxBound + uniformWord64 :: g -> m Word64 + uniformWord64 = uniformWord64R maxBound + +data SysRandom + +data Gen s + +-- Example /dev/urandom +instance MonadIO m => MonadRandom SysRandom m + +-- Example mwc-random +instance (s ~ PrimState m, PrimMonad m) => MonadRandom (Gen s) m + +-- | An opaque data type that carries the state of a pure generator at the type level +data GenState s g = GenState + +instance (MonadState g m, RandomGen g) => MonadRandom (GenState s g) m where + type Seed (GenState s g) = GenSeed g + restore s = GenState <$ put (mkGen s) + save _ = saveGen <$> get + uniformWord32R r _ = state (genWord32R r) + uniformWord64R r _ = state (genWord64R r) + uniformWord8 _ = state genWord8 + uniformWord16 _ = state genWord16 + uniformWord32 _ = state genWord32 + uniformWord64 _ = state genWord64 + +genRandom :: (RandomGen g, Random a, MonadState g m) => m a +genRandom = randomM GenState + +genRandomR :: (RandomGen g, Random a, MonadState g m) => (a, a) -> m a +genRandomR r = randomRM r GenState + +runStateGen :: g -> State g a -> (a, g) +runStateGen g = flip runState g + +runStateGen_ :: (RandomGen g, Random a) => g -> State g a -> a +runStateGen_ g = fst . flip runState g + +runStateTGen :: (RandomGen g, Random a) => g -> StateT g m a -> m (a, g) +runStateTGen g = flip runStateT g + +runStateTGen_ :: (RandomGen g, Random a, Functor f) => g -> StateT g f a -> f a +runStateTGen_ g = fmap fst . flip runStateT g + {- | The 'StdGen' instance of 'RandomGen' has a 'genRange' of at least 30 bits. @@ -215,8 +312,12 @@ data StdGen = StdGen !Int32 !Int32 instance RandomGen StdGen where + type GenSeed StdGen = Int next = stdNext genRange _ = stdRange + mkGen = mkStdGen + saveGen (StdGen h _) = fromIntegral h + -- ^ this is likely incorrect, but we'll switch to splitmix anyways #ifdef ENABLE_SPLITTABLEGEN instance SplittableGen StdGen where @@ -284,13 +385,19 @@ Minimal complete definition: 'randomR' and 'random'. -} class Random a where + randomM :: MonadRandom g m => g -> m a + + randomRM :: MonadRandom g m => (a, a) -> g -> m a + -- | Takes a range /(lo,hi)/ and a random number generator -- /g/, and returns a random value uniformly distributed in the closed -- interval /[lo,hi]/, together with a new generator. It is unspecified -- what happens if /lo>hi/. For continuous types there is no requirement -- that the values /lo/ and /hi/ are ever produced, but they may be, -- depending on the implementation and the interval. - randomR :: RandomGen g => (a,a) -> g -> (a,g) + {-# INLINE randomR #-} + randomR :: RandomGen g => (a, a) -> g -> (a, g) + randomR r g = runStateGen g (genRandomR r) -- | The same as 'randomR', but using a default range determined by the type: -- @@ -301,7 +408,9 @@ class Random a where -- @[0,1)@. -- -- * For 'Integer', the range is (arbitrarily) the range of 'Int'. + {-# INLINE random #-} random :: RandomGen g => g -> (a, g) + random g = runStateGen g genRandom -- | Plural variant of 'randomR', producing an infinite list of -- random values instead of returning a new generator. @@ -342,40 +451,169 @@ instance Random Integer where randomR ival g = randomIvalInteger ival g random g = randomR (toInteger (minBound::Int), toInteger (maxBound::Int)) g -instance Random Int where randomR = randomIvalIntegral; random = randomBounded -instance Random Int8 where randomR = randomIvalIntegral; random = randomBounded -instance Random Int16 where randomR = randomIvalIntegral; random = randomBounded -instance Random Int32 where randomR = randomIvalIntegral; random = randomBounded -instance Random Int64 where randomR = randomIvalIntegral; random = randomBounded +instance Random Int8 where + randomR = bitmaskWithRejection + random = first (fromIntegral :: Word8 -> Int8) . genWord8 + randomM = fmap (fromIntegral :: Word8 -> Int8) . uniformWord8 + randomRM = bitmaskWithRejectionPrim +instance Random Int16 where + randomR = bitmaskWithRejection + random = first (fromIntegral :: Word16 -> Int16) . genWord16 + randomM = fmap (fromIntegral :: Word16 -> Int16) . uniformWord16 + randomRM = bitmaskWithRejectionPrim +instance Random Int32 where + randomR = bitmaskWithRejection + random = first (fromIntegral :: Word32 -> Int32) . genWord32 + randomM = fmap (fromIntegral :: Word32 -> Int32) . uniformWord32 + randomRM = bitmaskWithRejectionPrim +instance Random Int64 where + randomR = bitmaskWithRejection + random = first (fromIntegral :: Word64 -> Int64) . genWord64 + randomM = fmap (fromIntegral :: Word64 -> Int64) . uniformWord64 + randomRM = bitmaskWithRejectionPrim + +instance Random Int where + randomR = bitmaskWithRejection + randomRM = bitmaskWithRejectionPrim +#if WORD_SIZE_IN_BITS < 64 + random = first (fromIntegral :: Word32 -> Int) . genWord32 + randomM = fmap (fromIntegral :: Word32 -> Int) . uniformWord32 +#else + random = first (fromIntegral :: Word64 -> Int) . genWord64 + randomM = fmap (fromIntegral :: Word64 -> Int) . uniformWord64 +#endif + #ifndef __NHC__ -- Word is a type synonym in nhc98. -instance Random Word where randomR = randomIvalIntegral; random = randomBounded +instance Random Word where + randomR = bitmaskWithRejection + randomRM = bitmaskWithRejectionPrim +#if WORD_SIZE_IN_BITS < 64 + random = first (fromIntegral :: Word32 -> Word) . genWord32 + randomM = fmap (fromIntegral :: Word32 -> Word) . uniformWord32 +#else + random = first (fromIntegral :: Word64 -> Word) . genWord64 + randomM = fmap (fromIntegral :: Word64 -> Word) . uniformWord64 +#endif #endif -instance Random Word8 where randomR = randomIvalIntegral; random = randomBounded -instance Random Word16 where randomR = randomIvalIntegral; random = randomBounded -instance Random Word32 where randomR = randomIvalIntegral; random = randomBounded -instance Random Word64 where randomR = randomIvalIntegral; random = randomBounded - -instance Random CChar where randomR = randomIvalIntegral; random = randomBounded -instance Random CSChar where randomR = randomIvalIntegral; random = randomBounded -instance Random CUChar where randomR = randomIvalIntegral; random = randomBounded -instance Random CShort where randomR = randomIvalIntegral; random = randomBounded -instance Random CUShort where randomR = randomIvalIntegral; random = randomBounded -instance Random CInt where randomR = randomIvalIntegral; random = randomBounded -instance Random CUInt where randomR = randomIvalIntegral; random = randomBounded -instance Random CLong where randomR = randomIvalIntegral; random = randomBounded -instance Random CULong where randomR = randomIvalIntegral; random = randomBounded -instance Random CPtrdiff where randomR = randomIvalIntegral; random = randomBounded -instance Random CSize where randomR = randomIvalIntegral; random = randomBounded -instance Random CWchar where randomR = randomIvalIntegral; random = randomBounded -instance Random CSigAtomic where randomR = randomIvalIntegral; random = randomBounded -instance Random CLLong where randomR = randomIvalIntegral; random = randomBounded -instance Random CULLong where randomR = randomIvalIntegral; random = randomBounded -instance Random CIntPtr where randomR = randomIvalIntegral; random = randomBounded -instance Random CUIntPtr where randomR = randomIvalIntegral; random = randomBounded -instance Random CIntMax where randomR = randomIvalIntegral; random = randomBounded -instance Random CUIntMax where randomR = randomIvalIntegral; random = randomBounded + +instance Random Word8 where + randomR = bitmaskWithRejection + random = genWord8 + randomM = uniformWord8 + randomRM = bitmaskWithRejectionPrim +instance Random Word16 where + randomR = bitmaskWithRejection + random = genWord16 + randomM = uniformWord16 + randomRM = bitmaskWithRejectionPrim +instance Random Word32 where + randomR = bitmaskWithRejection + random = genWord32 + randomM = uniformWord32 + randomRM = bitmaskWithRejectionPrim +instance Random Word64 where + randomR = bitmaskWithRejection + random = genWord64 + randomM = uniformWord64 + randomRM = bitmaskWithRejectionPrim + +instance Random CChar where + randomR (CChar b, CChar t) = first CChar . randomR (b, t) + random = first CChar . random + randomM = fmap CChar . randomM + randomRM (CChar b, CChar t) = fmap CChar . randomRM (b, t) +instance Random CSChar where + randomR (CSChar b, CSChar t) = first CSChar . randomR (b, t) + random = first CSChar . random + randomM = fmap CSChar . randomM + randomRM (CSChar b, CSChar t) = fmap CSChar . randomRM (b, t) +instance Random CUChar where + randomR (CUChar b, CUChar t) = first CUChar . randomR (b, t) + random = first CUChar . random + randomM = fmap CUChar . randomM + randomRM (CUChar b, CUChar t) = fmap CUChar . randomRM (b, t) +instance Random CShort where + randomR (CShort b, CShort t) = first CShort . randomR (b, t) + random = first CShort . random + randomM = fmap CShort . randomM + randomRM (CShort b, CShort t) = fmap CShort . randomRM (b, t) +instance Random CUShort where + randomR (CUShort b, CUShort t) = first CUShort . randomR (b, t) + random = first CUShort . random + randomM = fmap CUShort . randomM + randomRM (CUShort b, CUShort t) = fmap CUShort . randomRM (b, t) +instance Random CInt where + randomR (CInt b, CInt t) = first CInt . randomR (b, t) + random = first CInt . random + randomM = fmap CInt . randomM + randomRM (CInt b, CInt t) = fmap CInt . randomRM (b, t) +instance Random CUInt where + randomR (CUInt b, CUInt t) = first CUInt . randomR (b, t) + random = first CUInt . random + randomM = fmap CUInt . randomM + randomRM (CUInt b, CUInt t) = fmap CUInt . randomRM (b, t) +instance Random CLong where + randomR (CLong b, CLong t) = first CLong . randomR (b, t) + random = first CLong . random + randomM = fmap CLong . randomM + randomRM (CLong b, CLong t) = fmap CLong . randomRM (b, t) +instance Random CULong where + randomR (CULong b, CULong t) = first CULong . randomR (b, t) + random = first CULong . random + randomM = fmap CULong . randomM + randomRM (CULong b, CULong t) = fmap CULong . randomRM (b, t) +instance Random CPtrdiff where + randomR (CPtrdiff b, CPtrdiff t) = first CPtrdiff . randomR (b, t) + random = first CPtrdiff . random + randomM = fmap CPtrdiff . randomM + randomRM (CPtrdiff b, CPtrdiff t) = fmap CPtrdiff . randomRM (b, t) +instance Random CSize where + randomR (CSize b, CSize t) = first CSize . randomR (b, t) + random = first CSize . random + randomM = fmap CSize . randomM + randomRM (CSize b, CSize t) = fmap CSize . randomRM (b, t) +instance Random CWchar where + randomR (CWchar b, CWchar t) = first CWchar . randomR (b, t) + random = first CWchar . random + randomM = fmap CWchar . randomM + randomRM (CWchar b, CWchar t) = fmap CWchar . randomRM (b, t) +instance Random CSigAtomic where + randomR (CSigAtomic b, CSigAtomic t) = first CSigAtomic . randomR (b, t) + random = first CSigAtomic . random + randomM = fmap CSigAtomic . randomM + randomRM (CSigAtomic b, CSigAtomic t) = fmap CSigAtomic . randomRM (b, t) +instance Random CLLong where + randomR (CLLong b, CLLong t) = first CLLong . randomR (b, t) + random = first CLLong . random + randomM = fmap CLLong . randomM + randomRM (CLLong b, CLLong t) = fmap CLLong . randomRM (b, t) +instance Random CULLong where + randomR (CULLong b, CULLong t) = first CULLong . randomR (b, t) + random = first CULLong . random + randomM = fmap CULLong . randomM + randomRM (CULLong b, CULLong t) = fmap CULLong . randomRM (b, t) +instance Random CIntPtr where + randomR (CIntPtr b, CIntPtr t) = first CIntPtr . randomR (b, t) + random = first CIntPtr . random + randomM = fmap CIntPtr . randomM + randomRM (CIntPtr b, CIntPtr t) = fmap CIntPtr . randomRM (b, t) +instance Random CUIntPtr where + randomR (CUIntPtr b, CUIntPtr t) = first CUIntPtr . randomR (b, t) + random = first CUIntPtr . random + randomM = fmap CUIntPtr . randomM + randomRM (CUIntPtr b, CUIntPtr t) = fmap CUIntPtr . randomRM (b, t) +instance Random CIntMax where + randomR (CIntMax b, CIntMax t) = first CIntMax . randomR (b, t) + random = first CIntMax . random + randomM = fmap CIntMax . randomM + randomRM (CIntMax b, CIntMax t) = fmap CIntMax . randomRM (b, t) +instance Random CUIntMax where + randomR (CUIntMax b, CUIntMax t) = first CUIntMax . randomR (b, t) + random = first CUIntMax . random + randomM = fmap CUIntMax . randomM + randomRM (CUIntMax b, CUIntMax t) = fmap CUIntMax . randomRM (b, t) instance Random Char where randomR (a,b) g = @@ -405,9 +643,12 @@ randomRFloating (l,h) g | otherwise = let (coef,g') = random g in (2.0 * (0.5*l + coef * (0.5*h - 0.5*l)), g') -- avoid overflow -instance Random Double where - randomR = randomRFloating - random rng = +-- instance Random Double where +-- randomR = randomRFloating +-- -- random = nextDouble + +randomDouble :: RandomGen b => b -> (Double, b) +randomDouble rng = case random rng of (x,rng') -> -- We use 53 bits of randomness corresponding to the 53 bit significand: @@ -416,10 +657,14 @@ instance Random Double where where twoto53 = (2::Int64) ^ (53::Int64) mask53 = twoto53 - 1 - -instance Random Float where - randomR = randomRFloating - random rng = + + +-- instance Random Float where +-- randomR = randomRFloating +-- -- random = nextFloat + +randomFloat :: RandomGen b => b -> (Float, b) +randomFloat rng = -- TODO: Faster to just use 'next' IF it generates enough bits of randomness. case random rng of (x,rng') -> @@ -434,19 +679,19 @@ instance Random Float where twoto24 = (2::Int32) ^ (24::Int32) -- CFloat/CDouble are basically the same as a Float/Double: -instance Random CFloat where - randomR = randomRFloating - random rng = case random rng of - (x,rng') -> (realToFrac (x::Float), rng') - -instance Random CDouble where - randomR = randomRFloating - -- A MYSTERY: - -- Presently, this is showing better performance than the Double instance: - -- (And yet, if the Double instance uses randomFrac then its performance is much worse!) - random = randomFrac +-- instance Random CFloat where +-- randomR = randomRFloating -- random rng = case random rng of - -- (x,rng') -> (realToFrac (x::Double), rng') + -- (x,rng') -> (realToFrac (x::Float), rng') + +-- instance Random CDouble where +-- randomR = randomRFloating +-- -- A MYSTERY: +-- -- Presently, this is showing better performance than the Double instance: +-- -- (And yet, if the Double instance uses randomFrac then its performance is much worse!) +-- random = randomFrac +-- -- random rng = case random rng of +-- -- (x,rng') -> (realToFrac (x::Double), rng') mkStdRNG :: Integer -> IO StdGen mkStdRNG o = do @@ -507,6 +752,48 @@ randomIvalDouble (l,h) fromDouble rng in (scaled_x, rng') + +bitmaskWithRejection :: + (RandomGen g, FiniteBits a, Num a, Ord a, Random a) + => (a, a) + -> g + -> (a, g) +bitmaskWithRejection (bottom, top) + | bottom > top = bitmaskWithRejection (top, bottom) + | bottom == top = (,) top + | otherwise = first (bottom +) . go + where + range = top - bottom + mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) + go g = + let (x, g') = random g + x' = x .&. mask + in if x' >= range + then go g' + else (x', g') +{-# INLINE bitmaskWithRejection #-} + +bitmaskWithRejectionPrim :: + (MonadRandom g m, FiniteBits a, Num a, Ord a, Random a) + => (a, a) + -> g + -> m a +bitmaskWithRejectionPrim (bottom, top) gen + | bottom > top = bitmaskWithRejectionPrim (top, bottom) gen + | bottom == top = pure top + | otherwise = (bottom +) <$> go + where + range = top - bottom + mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) + go = do + x <- randomM gen + let x' = x .&. mask + if x' >= range + then go + else pure x' +{-# INLINE bitmaskWithRejectionPrim #-} + + int32Count :: Integer int32Count = toInteger (maxBound::Int32) - toInteger (minBound::Int32) + 1 -- GHC ticket #3982 diff --git a/random.cabal b/random.cabal index fd29840fb..15978d239 100644 --- a/random.cabal +++ b/random.cabal @@ -34,9 +34,11 @@ cabal-version: >= 1.8 Library exposed-modules: System.Random - extensions: CPP GHC-Options: -O2 - build-depends: base >= 3 && < 5, time + build-depends: base >= 3 && < 5 + , primitive + , time + , mtl source-repository head type: git From a0b17bdc21a6665de8f0c1c15137bca8f8a02ddf Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sat, 22 Feb 2020 21:51:53 +0300 Subject: [PATCH 002/170] Add some examples and an instance for mwc-random --- System/Random.hs | 85 ++++++++++++++++++++++++++++++++++-------------- random.cabal | 1 + 2 files changed, 62 insertions(+), 24 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index ae196720a..a0bb74376 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -89,6 +89,7 @@ import Data.Functor.Identity import Data.Int import Data.Word import Foreign.C.Types +import qualified System.Random.MWC as MWC #ifdef __NHC__ import CPUTime ( getCPUTime ) @@ -239,21 +240,28 @@ class Monad m => MonadRandom g m where uniformWord64 :: g -> m Word64 uniformWord64 = uniformWord64R maxBound -data SysRandom - -data Gen s +data SysRandom = SysRandom -- Example /dev/urandom instance MonadIO m => MonadRandom SysRandom m -- Example mwc-random -instance (s ~ PrimState m, PrimMonad m) => MonadRandom (Gen s) m +instance (s ~ PrimState m, PrimMonad m) => MonadRandom (MWC.Gen s) m where + type Seed (MWC.Gen s) = MWC.Seed + restore = MWC.restore + save = MWC.save + uniformWord32R u = MWC.uniformR (0, u) + uniformWord64R u = MWC.uniformR (0, u) + uniformWord8 = MWC.uniform + uniformWord16 = MWC.uniform + uniformWord32 = MWC.uniform + uniformWord64 = MWC.uniform -- | An opaque data type that carries the state of a pure generator at the type level -data GenState s g = GenState +data GenState g = GenState -instance (MonadState g m, RandomGen g) => MonadRandom (GenState s g) m where - type Seed (GenState s g) = GenSeed g +instance (MonadState g m, RandomGen g) => MonadRandom (GenState g) m where + type Seed (GenState g) = GenSeed g restore s = GenState <$ put (mkGen s) save _ = saveGen <$> get uniformWord32R r _ = state (genWord32R r) @@ -272,15 +280,44 @@ genRandomR r = randomRM r GenState runStateGen :: g -> State g a -> (a, g) runStateGen g = flip runState g -runStateGen_ :: (RandomGen g, Random a) => g -> State g a -> a +runStateGen_ :: g -> State g a -> a runStateGen_ g = fst . flip runState g -runStateTGen :: (RandomGen g, Random a) => g -> StateT g m a -> m (a, g) +runStateTGen :: g -> StateT g m a -> m (a, g) runStateTGen g = flip runStateT g -runStateTGen_ :: (RandomGen g, Random a, Functor f) => g -> StateT g f a -> f a +runStateTGen_ :: Functor f => g -> StateT g f a -> f a runStateTGen_ g = fmap fst . flip runStateT g +randomList :: (Random a, RandomGen g, Num a) => Int -> g -> ([a], g) +randomList n g = runStateGen g $ replicateM n (genRandomR (1, 6)) + + +-- | Example: +-- +-- λ> runStateGen_ (mkGen 217 :: StdGen) (randomListM GenState 10) :: [Word64] +-- [1,2,3,5,5,2,5,1,4,1] +randomListM :: (Random a, MonadRandom g m, Num a) => g -> Int -> m [a] +randomListM gen n = replicateM n (randomRM (1, 6) gen) + +rlist :: Int -> ([Word64], [Word64]) +rlist n = (xs, ys) + where + xs = runStateGen_ (mkGen 217 :: StdGen) (randomListM GenState n) :: [Word64] + ys = runST $ do + gen <- MWC.create + randomListM gen n + + +randomListM' :: MonadRandom g m => Seed g -> Int -> m (g, [Word64]) +randomListM' seed n = do + gen <- restore seed + xs <- replicateM n (randomRM (1, 6) gen) + return (gen, xs) + +-- someActionM :: (RandomGen g, Random a, MonadState g m, Num a) => Int -> m [a] +-- someActionM n = replicateM n (genRandomR (1, 6)) + {- | The 'StdGen' instance of 'RandomGen' has a 'genRange' of at least 30 bits. @@ -455,26 +492,26 @@ instance Random Int8 where randomR = bitmaskWithRejection random = first (fromIntegral :: Word8 -> Int8) . genWord8 randomM = fmap (fromIntegral :: Word8 -> Int8) . uniformWord8 - randomRM = bitmaskWithRejectionPrim + randomRM = bitmaskWithRejectionM instance Random Int16 where randomR = bitmaskWithRejection random = first (fromIntegral :: Word16 -> Int16) . genWord16 randomM = fmap (fromIntegral :: Word16 -> Int16) . uniformWord16 - randomRM = bitmaskWithRejectionPrim + randomRM = bitmaskWithRejectionM instance Random Int32 where randomR = bitmaskWithRejection random = first (fromIntegral :: Word32 -> Int32) . genWord32 randomM = fmap (fromIntegral :: Word32 -> Int32) . uniformWord32 - randomRM = bitmaskWithRejectionPrim + randomRM = bitmaskWithRejectionM instance Random Int64 where randomR = bitmaskWithRejection random = first (fromIntegral :: Word64 -> Int64) . genWord64 randomM = fmap (fromIntegral :: Word64 -> Int64) . uniformWord64 - randomRM = bitmaskWithRejectionPrim + randomRM = bitmaskWithRejectionM instance Random Int where randomR = bitmaskWithRejection - randomRM = bitmaskWithRejectionPrim + randomRM = bitmaskWithRejectionM #if WORD_SIZE_IN_BITS < 64 random = first (fromIntegral :: Word32 -> Int) . genWord32 randomM = fmap (fromIntegral :: Word32 -> Int) . uniformWord32 @@ -488,7 +525,7 @@ instance Random Int where -- Word is a type synonym in nhc98. instance Random Word where randomR = bitmaskWithRejection - randomRM = bitmaskWithRejectionPrim + randomRM = bitmaskWithRejectionM #if WORD_SIZE_IN_BITS < 64 random = first (fromIntegral :: Word32 -> Word) . genWord32 randomM = fmap (fromIntegral :: Word32 -> Word) . uniformWord32 @@ -502,22 +539,22 @@ instance Random Word8 where randomR = bitmaskWithRejection random = genWord8 randomM = uniformWord8 - randomRM = bitmaskWithRejectionPrim + randomRM = bitmaskWithRejectionM instance Random Word16 where randomR = bitmaskWithRejection random = genWord16 randomM = uniformWord16 - randomRM = bitmaskWithRejectionPrim + randomRM = bitmaskWithRejectionM instance Random Word32 where randomR = bitmaskWithRejection random = genWord32 randomM = uniformWord32 - randomRM = bitmaskWithRejectionPrim + randomRM = bitmaskWithRejectionM instance Random Word64 where randomR = bitmaskWithRejection random = genWord64 randomM = uniformWord64 - randomRM = bitmaskWithRejectionPrim + randomRM = bitmaskWithRejectionM instance Random CChar where randomR (CChar b, CChar t) = first CChar . randomR (b, t) @@ -773,13 +810,13 @@ bitmaskWithRejection (bottom, top) else (x', g') {-# INLINE bitmaskWithRejection #-} -bitmaskWithRejectionPrim :: +bitmaskWithRejectionM :: (MonadRandom g m, FiniteBits a, Num a, Ord a, Random a) => (a, a) -> g -> m a -bitmaskWithRejectionPrim (bottom, top) gen - | bottom > top = bitmaskWithRejectionPrim (top, bottom) gen +bitmaskWithRejectionM (bottom, top) gen + | bottom > top = bitmaskWithRejectionM (top, bottom) gen | bottom == top = pure top | otherwise = (bottom +) <$> go where @@ -791,7 +828,7 @@ bitmaskWithRejectionPrim (bottom, top) gen if x' >= range then go else pure x' -{-# INLINE bitmaskWithRejectionPrim #-} +{-# INLINE bitmaskWithRejectionM #-} int32Count :: Integer diff --git a/random.cabal b/random.cabal index 15978d239..e8bb0b565 100644 --- a/random.cabal +++ b/random.cabal @@ -39,6 +39,7 @@ Library , primitive , time , mtl + , mwc-random source-repository head type: git From 6790adee0fbbb3a9ab578789ab3453fde3ce20ac Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sat, 22 Feb 2020 22:12:14 +0300 Subject: [PATCH 003/170] Couple extra constraints --- System/Random.hs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index a0bb74376..56ca54786 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -277,20 +277,20 @@ genRandom = randomM GenState genRandomR :: (RandomGen g, Random a, MonadState g m) => (a, a) -> m a genRandomR r = randomRM r GenState -runStateGen :: g -> State g a -> (a, g) +runStateGen :: RandomGen g => g -> State g a -> (a, g) runStateGen g = flip runState g -runStateGen_ :: g -> State g a -> a +runStateGen_ :: RandomGen g => g -> State g a -> a runStateGen_ g = fst . flip runState g -runStateTGen :: g -> StateT g m a -> m (a, g) +runStateTGen :: RandomGen g => g -> StateT g m a -> m (a, g) runStateTGen g = flip runStateT g -runStateTGen_ :: Functor f => g -> StateT g f a -> f a +runStateTGen_ :: (RandomGen g, Functor f) => g -> StateT g f a -> f a runStateTGen_ g = fmap fst . flip runStateT g -randomList :: (Random a, RandomGen g, Num a) => Int -> g -> ([a], g) -randomList n g = runStateGen g $ replicateM n (genRandomR (1, 6)) +randomList :: (Random a, RandomGen g, Num a) => Int -> g -> [a] +randomList n g = runStateGen_ g $ replicateM n (genRandomR (1, 6)) -- | Example: @@ -304,9 +304,7 @@ rlist :: Int -> ([Word64], [Word64]) rlist n = (xs, ys) where xs = runStateGen_ (mkGen 217 :: StdGen) (randomListM GenState n) :: [Word64] - ys = runST $ do - gen <- MWC.create - randomListM gen n + ys = runST $ MWC.create >>= (`randomListM` n) randomListM' :: MonadRandom g m => Seed g -> Int -> m (g, [Word64]) From bb10f39bd22b5114d65cd711a866b7c1ce26e5b5 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 23 Feb 2020 01:08:30 +0300 Subject: [PATCH 004/170] A few inline pragmas. Check performance --- System/Random.hs | 102 ++++++++++++++++++++++++++++------------------- 1 file changed, 62 insertions(+), 40 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 56ca54786..c8f171d33 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -43,40 +43,47 @@ #include "MachDeps.h" -module System.Random where --- ( +module System.Random + ( --- -- $intro + -- $intro --- -- * Random number generators + -- * Random number generators --- #ifdef ENABLE_SPLITTABLEGEN --- RandomGen(..) --- , SplittableGen(..) --- #else --- RandomGen(..) --- #endif --- , RandomPrimGen(..) --- -- ** Standard random number generators --- , StdGen --- , mkStdGen +#ifdef ENABLE_SPLITTABLEGEN + RandomGen(..) + , SplittableGen(..) +#else + RandomGen(..) +#endif + , MonadRandom(..) + -- ** Standard random number generators + , StdGen + , mkStdGen --- -- ** The global random number generator + , genRandom + , genRandomR + , runStateGen + , runStateGen_ + , runStateTGen + , runStateTGen_ --- -- $globalrng + -- ** The global random number generator --- , getStdRandom --- , getStdGen --- , setStdGen --- , newStdGen + -- $globalrng --- -- * Random values of various types --- , Random(..) + , getStdRandom + , getStdGen + , setStdGen + , newStdGen --- -- * References --- -- $references + -- * Random values of various types + , Random(..) --- ) where + -- * References + -- $references + + ) where import Prelude @@ -254,7 +261,9 @@ instance (s ~ PrimState m, PrimMonad m) => MonadRandom (MWC.Gen s) m where uniformWord64R u = MWC.uniformR (0, u) uniformWord8 = MWC.uniform uniformWord16 = MWC.uniform + {-# INLINE uniformWord32 #-} uniformWord32 = MWC.uniform + {-# INLINE uniformWord64 #-} uniformWord64 = MWC.uniform -- | An opaque data type that carries the state of a pure generator at the type level @@ -420,9 +429,10 @@ Minimal complete definition: 'randomR' and 'random'. -} class Random a where + randomRM :: MonadRandom g m => (a, a) -> g -> m a + randomM :: MonadRandom g m => g -> m a - randomRM :: MonadRandom g m => (a, a) -> g -> m a -- | Takes a range /(lo,hi)/ and a random number generator -- /g/, and returns a random value uniformly distributed in the closed @@ -518,9 +528,6 @@ instance Random Int where randomM = fmap (fromIntegral :: Word64 -> Int) . uniformWord64 #endif - -#ifndef __NHC__ --- Word is a type synonym in nhc98. instance Random Word where randomR = bitmaskWithRejection randomRM = bitmaskWithRejectionM @@ -531,27 +538,42 @@ instance Random Word where random = first (fromIntegral :: Word64 -> Word) . genWord64 randomM = fmap (fromIntegral :: Word64 -> Word) . uniformWord64 #endif -#endif instance Random Word8 where + {-# INLINE randomR #-} randomR = bitmaskWithRejection + {-# INLINE random #-} random = genWord8 - randomM = uniformWord8 - randomRM = bitmaskWithRejectionM + {-# INLINE randomRM #-} + randomRM = bitmaskWithRejectionM + {-# INLINE randomM #-} + randomM = uniformWord8 instance Random Word16 where + {-# INLINE randomR #-} randomR = bitmaskWithRejection + {-# INLINE random #-} random = genWord16 - randomM = uniformWord16 - randomRM = bitmaskWithRejectionM + {-# INLINE randomRM #-} + randomRM = bitmaskWithRejectionM + {-# INLINE randomM #-} + randomM = uniformWord16 instance Random Word32 where + {-# INLINE randomR #-} randomR = bitmaskWithRejection + {-# INLINE random #-} random = genWord32 + {-# INLINE randomM #-} randomM = uniformWord32 + {-# INLINE randomRM #-} randomRM = bitmaskWithRejectionM instance Random Word64 where + {-# INLINE randomR #-} randomR = bitmaskWithRejection + {-# INLINE random #-} random = genWord64 + {-# INLINE randomM #-} randomM = uniformWord64 + {-# INLINE randomRM #-} randomRM = bitmaskWithRejectionM instance Random CChar where @@ -678,9 +700,9 @@ randomRFloating (l,h) g | otherwise = let (coef,g') = random g in (2.0 * (0.5*l + coef * (0.5*h - 0.5*l)), g') -- avoid overflow --- instance Random Double where --- randomR = randomRFloating --- -- random = nextDouble +instance Random Double where + randomR = randomRFloating + random = randomDouble randomDouble :: RandomGen b => b -> (Double, b) randomDouble rng = @@ -694,9 +716,9 @@ randomDouble rng = mask53 = twoto53 - 1 --- instance Random Float where --- randomR = randomRFloating --- -- random = nextFloat +instance Random Float where + randomR = randomRFloating + random = randomFloat randomFloat :: RandomGen b => b -> (Float, b) randomFloat rng = From d3f61e8b6e5840fc6779224779103a119ab86115 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 23 Feb 2020 03:18:53 +0300 Subject: [PATCH 005/170] Rename `GenState` to `PureGen` --- System/Random.hs | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index c8f171d33..fe5442596 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -98,22 +98,16 @@ import Data.Word import Foreign.C.Types import qualified System.Random.MWC as MWC -#ifdef __NHC__ -import CPUTime ( getCPUTime ) -import Foreign.Ptr ( Ptr, nullPtr ) -import Foreign.C ( CTime, CUInt ) -#else import System.CPUTime ( getCPUTime ) import Data.Time ( getCurrentTime, UTCTime(..) ) import Data.Ratio ( numerator, denominator ) -#endif import Data.Char ( isSpace, chr, ord ) import System.IO.Unsafe ( unsafePerformIO ) -import Data.IORef ( IORef, newIORef, readIORef, writeIORef ) +import Data.IORef ( IORef, newIORef, readIORef, writeIORef, #if MIN_VERSION_base (4,6,0) -import Data.IORef ( atomicModifyIORef' ) + atomicModifyIORef' ) #else -import Data.IORef ( atomicModifyIORef ) + atomicModifyIORef ) #endif import Numeric ( readDec ) @@ -267,11 +261,11 @@ instance (s ~ PrimState m, PrimMonad m) => MonadRandom (MWC.Gen s) m where uniformWord64 = MWC.uniform -- | An opaque data type that carries the state of a pure generator at the type level -data GenState g = GenState +data PureGen g = PureGen -instance (MonadState g m, RandomGen g) => MonadRandom (GenState g) m where - type Seed (GenState g) = GenSeed g - restore s = GenState <$ put (mkGen s) +instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) m where + type Seed (PureGen g) = GenSeed g + restore s = PureGen <$ put (mkGen s) save _ = saveGen <$> get uniformWord32R r _ = state (genWord32R r) uniformWord64R r _ = state (genWord64R r) @@ -281,19 +275,19 @@ instance (MonadState g m, RandomGen g) => MonadRandom (GenState g) m where uniformWord64 _ = state genWord64 genRandom :: (RandomGen g, Random a, MonadState g m) => m a -genRandom = randomM GenState +genRandom = randomM PureGen genRandomR :: (RandomGen g, Random a, MonadState g m) => (a, a) -> m a -genRandomR r = randomRM r GenState +genRandomR r = randomRM r PureGen runStateGen :: RandomGen g => g -> State g a -> (a, g) -runStateGen g = flip runState g +runStateGen = flip runState runStateGen_ :: RandomGen g => g -> State g a -> a runStateGen_ g = fst . flip runState g runStateTGen :: RandomGen g => g -> StateT g m a -> m (a, g) -runStateTGen g = flip runStateT g +runStateTGen = flip runStateT runStateTGen_ :: (RandomGen g, Functor f) => g -> StateT g f a -> f a runStateTGen_ g = fmap fst . flip runStateT g @@ -304,7 +298,7 @@ randomList n g = runStateGen_ g $ replicateM n (genRandomR (1, 6)) -- | Example: -- --- λ> runStateGen_ (mkGen 217 :: StdGen) (randomListM GenState 10) :: [Word64] +-- λ> runStateGen_ (mkGen 217 :: StdGen) (randomListM PureGen 10) :: [Word64] -- [1,2,3,5,5,2,5,1,4,1] randomListM :: (Random a, MonadRandom g m, Num a) => g -> Int -> m [a] randomListM gen n = replicateM n (randomRM (1, 6) gen) @@ -312,7 +306,7 @@ randomListM gen n = replicateM n (randomRM (1, 6) gen) rlist :: Int -> ([Word64], [Word64]) rlist n = (xs, ys) where - xs = runStateGen_ (mkGen 217 :: StdGen) (randomListM GenState n) :: [Word64] + xs = runStateGen_ (mkGen 217 :: StdGen) (randomListM PureGen n) :: [Word64] ys = runST $ MWC.create >>= (`randomListM` n) From 4bb37cfd588996c55e62ec4f908b8ea7d99a38f6 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 23 Feb 2020 03:23:50 +0300 Subject: [PATCH 006/170] Remove support for non-ghc compilers --- System/Random.hs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index fe5442596..9a9ea462d 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -41,8 +41,6 @@ -- ----------------------------------------------------------------------------- -#include "MachDeps.h" - module System.Random ( @@ -111,14 +109,7 @@ import Data.IORef ( IORef, newIORef, readIORef, writeIORef, #endif import Numeric ( readDec ) -#ifdef __GLASGOW_HASKELL__ import GHC.Exts ( build ) -#else --- | A dummy variant of build without fusion. -{-# INLINE build #-} -build :: ((a -> [a] -> [a]) -> [a] -> [a]) -> [a] -build g = g (:) [] -#endif #if !MIN_VERSION_base (4,6,0) atomicModifyIORef' :: IORef a -> (a -> (a,b)) -> IO b @@ -129,20 +120,11 @@ atomicModifyIORef' ref f = do b `seq` return b #endif --- The standard nhc98 implementation of Time.ClockTime does not match --- the extended one expected in this module, so we lash-up a quick --- replacement here. -#ifdef __NHC__ -foreign import ccall "time.h time" readtime :: Ptr CTime -> IO CTime -getTime :: IO (Integer, Integer) -getTime = do CTime t <- readtime nullPtr; return (toInteger t, 0) -#else getTime :: IO (Integer, Integer) getTime = do utc <- getCurrentTime let daytime = toRational $ utctDayTime utc return $ quotRem (numerator daytime) (denominator daytime) -#endif -- | The class 'RandomGen' provides a common interface to random number -- generators. From 7804b7e873560292c3ea44657181c6ee4410884c Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 23 Feb 2020 19:59:48 +0300 Subject: [PATCH 007/170] Removed `mkGen`, `saveGen` and `GenSeed` --- System/Random.hs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 9a9ea462d..84e2e6b92 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -136,13 +136,6 @@ getTime = do #endif class RandomGen g where - type GenSeed g :: * - type GenSeed g = Word64 - - mkGen :: GenSeed g -> g - - saveGen :: g -> GenSeed g - -- |The 'next' operation returns an 'Int' that is uniformly distributed -- in the range returned by 'genRange' (including both end points), -- and a new generator. @@ -246,9 +239,9 @@ instance (s ~ PrimState m, PrimMonad m) => MonadRandom (MWC.Gen s) m where data PureGen g = PureGen instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) m where - type Seed (PureGen g) = GenSeed g - restore s = PureGen <$ put (mkGen s) - save _ = saveGen <$> get + type Seed (PureGen g) = g + restore g = PureGen <$ put g + save _ = get uniformWord32R r _ = state (genWord32R r) uniformWord64R r _ = state (genWord64R r) uniformWord8 _ = state genWord8 @@ -288,7 +281,7 @@ randomListM gen n = replicateM n (randomRM (1, 6) gen) rlist :: Int -> ([Word64], [Word64]) rlist n = (xs, ys) where - xs = runStateGen_ (mkGen 217 :: StdGen) (randomListM PureGen n) :: [Word64] + xs = runStateGen_ (mkStdGen 217 :: StdGen) (randomListM PureGen n) :: [Word64] ys = runST $ MWC.create >>= (`randomListM` n) @@ -332,11 +325,8 @@ data StdGen = StdGen !Int32 !Int32 instance RandomGen StdGen where - type GenSeed StdGen = Int next = stdNext genRange _ = stdRange - mkGen = mkStdGen - saveGen (StdGen h _) = fromIntegral h -- ^ this is likely incorrect, but we'll switch to splitmix anyways #ifdef ENABLE_SPLITTABLEGEN From 04f271c291be0752c822c27b2d5181152ae88765 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 23 Feb 2020 04:50:38 +0300 Subject: [PATCH 008/170] Add functionality for generating a ByteArray --- System/Random.hs | 63 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 84e2e6b92..63517bb96 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1,6 +1,8 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} #if __GLASGOW_HASKELL__ >= 701 {-# LANGUAGE Trustworthy #-} @@ -78,6 +80,9 @@ module System.Random -- * Random values of various types , Random(..) + -- * Default generators + , uniformByteArrayPrim + -- * References -- $references @@ -90,8 +95,9 @@ import Control.Monad.ST import Control.Monad.Primitive import Control.Monad.State.Strict import Data.Bits -import Data.Functor.Identity import Data.Int +import Data.Primitive.Types (Prim) +import Data.Primitive.ByteArray import Data.Word import Foreign.C.Types import qualified System.Random.MWC as MWC @@ -160,6 +166,10 @@ class RandomGen g where genWord64R :: Word64 -> g -> (Word64, g) genWord64R m = randomIvalIntegral (minBound, m) + genBytes :: Int -> g -> (ByteArray, g) + genBytes n g = runST $ runStateTGen g $ uniformByteArrayPrim n PureGen + {-# INLINE genBytes #-} + -- |The 'genRange' operation yields the range of values returned by -- the generator. @@ -215,11 +225,55 @@ class Monad m => MonadRandom g m where uniformWord32 = uniformWord32R maxBound uniformWord64 :: g -> m Word64 uniformWord64 = uniformWord64R maxBound + uniformBytes :: Int -> g -> m ByteArray + default uniformBytes :: PrimMonad m => Int -> g -> m ByteArray + uniformBytes = uniformByteArrayPrim + {-# INLINE uniformBytes #-} + +uniformByteArrayPrim :: forall g m . (MonadRandom g m, PrimMonad m) => Int -> g -> m ByteArray +uniformByteArrayPrim n0 gen = do + let n = max 0 n0 + (n64, nrem) = n `quotRem` 8 + ma :: MutableByteArray (PrimState m) <- newByteArray n + let go :: (PrimMonad m, Prim a) => (g -> m a) -> Int -> Int -> m () + go f i k + | i < k = do + w <- f gen + writeByteArray ma i w + go f (i + 1) k + | otherwise = return () + go uniformWord64 0 n64 + go uniformWord8 (n - nrem) n + unsafeFreezeByteArray ma +{-# INLINE uniformByteArrayPrim #-} + +-- g = mkStdGen 217 +-- genBytes 20 g +-- -- TODO: benchmark this version against StateT version. +-- genByteArray :: RandomGen b => Int -> b -> (ByteArray, b) +-- genByteArray n0 g0 = runST $ do +-- let n = max 0 n0 +-- nrem = n `rem` 8 +-- n64 = n - nrem +-- ma <- newByteArray n +-- let go64 i g | i < n64 = +-- case genWord64 g of +-- (w64, g') -> writeByteArray ma i w64 >> go64 (i + 1) g' +-- | otherwise = pure g +-- go8 i g | i < n = +-- case genWord8 g of +-- (w8, g') -> writeByteArray ma i w8 >> go8 (i + 1) g' +-- | otherwise = pure g +-- g' <- go8 n64 =<< go64 0 g0 +-- a <- unsafeFreezeByteArray ma +-- pure (a, g') + data SysRandom = SysRandom -- Example /dev/urandom -instance MonadIO m => MonadRandom SysRandom m +instance MonadIO m => MonadRandom SysRandom m where + uniformBytes n gen = liftIO $ uniformByteArrayPrim n gen -- Example mwc-random instance (s ~ PrimState m, PrimMonad m) => MonadRandom (MWC.Gen s) m where @@ -248,6 +302,7 @@ instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) m where uniformWord16 _ = state genWord16 uniformWord32 _ = state genWord32 uniformWord64 _ = state genWord64 + uniformBytes n _ = state (genBytes n) genRandom :: (RandomGen g, Random a, MonadState g m) => m a genRandom = randomM PureGen @@ -255,6 +310,10 @@ genRandom = randomM PureGen genRandomR :: (RandomGen g, Random a, MonadState g m) => (a, a) -> m a genRandomR r = randomRM r PureGen +-- | Split current generator and update the state with one part, while returning the other. +splitGen :: (MonadState g m, RandomGen g) => m g +splitGen = state split + runStateGen :: RandomGen g => g -> State g a -> (a, g) runStateGen = flip runState From cbcca003beb9903358703da3a06bf63dbc1fe33a Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 23 Feb 2020 19:27:05 +0300 Subject: [PATCH 009/170] Random for word ranges --- System/Random.hs | 50 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 63517bb96..2edfe22ee 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -209,13 +209,16 @@ class SplittableGen g where class Monad m => MonadRandom g m where type Seed g :: * + {-# MINIMAL save,restore,(uniformWord32R|uniformWord32),(uniformWord64R|uniformWord64) #-} restore :: Seed g -> m g save :: g -> m (Seed g) -- | Generate `Word32` up to and including the supplied max value uniformWord32R :: Word32 -> g -> m Word32 + uniformWord32R = bitmaskWithRejection32M -- | Generate `Word64` up to and including the supplied max value uniformWord64R :: Word64 -> g -> m Word64 + uniformWord64R = bitmaskWithRejection64M uniformWord8 :: g -> m Word8 uniformWord8 = fmap fromIntegral . uniformWord32R (fromIntegral (maxBound :: Word8)) @@ -525,26 +528,26 @@ instance Random Int8 where randomR = bitmaskWithRejection random = first (fromIntegral :: Word8 -> Int8) . genWord8 randomM = fmap (fromIntegral :: Word8 -> Int8) . uniformWord8 - randomRM = bitmaskWithRejectionM + randomRM = bitmaskWithRejectionRM instance Random Int16 where randomR = bitmaskWithRejection random = first (fromIntegral :: Word16 -> Int16) . genWord16 randomM = fmap (fromIntegral :: Word16 -> Int16) . uniformWord16 - randomRM = bitmaskWithRejectionM + randomRM = bitmaskWithRejectionRM instance Random Int32 where randomR = bitmaskWithRejection random = first (fromIntegral :: Word32 -> Int32) . genWord32 randomM = fmap (fromIntegral :: Word32 -> Int32) . uniformWord32 - randomRM = bitmaskWithRejectionM + randomRM = bitmaskWithRejectionRM instance Random Int64 where randomR = bitmaskWithRejection random = first (fromIntegral :: Word64 -> Int64) . genWord64 randomM = fmap (fromIntegral :: Word64 -> Int64) . uniformWord64 - randomRM = bitmaskWithRejectionM + randomRM = bitmaskWithRejectionRM instance Random Int where randomR = bitmaskWithRejection - randomRM = bitmaskWithRejectionM + randomRM = bitmaskWithRejectionRM #if WORD_SIZE_IN_BITS < 64 random = first (fromIntegral :: Word32 -> Int) . genWord32 randomM = fmap (fromIntegral :: Word32 -> Int) . uniformWord32 @@ -555,7 +558,7 @@ instance Random Int where instance Random Word where randomR = bitmaskWithRejection - randomRM = bitmaskWithRejectionM + randomRM = bitmaskWithRejectionRM #if WORD_SIZE_IN_BITS < 64 random = first (fromIntegral :: Word32 -> Word) . genWord32 randomM = fmap (fromIntegral :: Word32 -> Word) . uniformWord32 @@ -570,7 +573,7 @@ instance Random Word8 where {-# INLINE random #-} random = genWord8 {-# INLINE randomRM #-} - randomRM = bitmaskWithRejectionM + randomRM = bitmaskWithRejectionRM {-# INLINE randomM #-} randomM = uniformWord8 instance Random Word16 where @@ -579,7 +582,7 @@ instance Random Word16 where {-# INLINE random #-} random = genWord16 {-# INLINE randomRM #-} - randomRM = bitmaskWithRejectionM + randomRM = bitmaskWithRejectionRM {-# INLINE randomM #-} randomM = uniformWord16 instance Random Word32 where @@ -590,7 +593,7 @@ instance Random Word32 where {-# INLINE randomM #-} randomM = uniformWord32 {-# INLINE randomRM #-} - randomRM = bitmaskWithRejectionM + randomRM = bitmaskWithRejectionRM instance Random Word64 where {-# INLINE randomR #-} randomR = bitmaskWithRejection @@ -599,7 +602,7 @@ instance Random Word64 where {-# INLINE randomM #-} randomM = uniformWord64 {-# INLINE randomRM #-} - randomRM = bitmaskWithRejectionM + randomRM = bitmaskWithRejectionRM instance Random CChar where randomR (CChar b, CChar t) = first CChar . randomR (b, t) @@ -855,13 +858,15 @@ bitmaskWithRejection (bottom, top) else (x', g') {-# INLINE bitmaskWithRejection #-} -bitmaskWithRejectionM :: + +-- FIXME This is likely incorrect for signed integrals. +bitmaskWithRejectionRM :: (MonadRandom g m, FiniteBits a, Num a, Ord a, Random a) => (a, a) -> g -> m a -bitmaskWithRejectionM (bottom, top) gen - | bottom > top = bitmaskWithRejectionM (top, bottom) gen +bitmaskWithRejectionRM (bottom, top) gen + | bottom > top = bitmaskWithRejectionRM (top, bottom) gen | bottom == top = pure top | otherwise = (bottom +) <$> go where @@ -873,8 +878,25 @@ bitmaskWithRejectionM (bottom, top) gen if x' >= range then go else pure x' -{-# INLINE bitmaskWithRejectionM #-} +{-# INLINE bitmaskWithRejectionRM #-} + +bitmaskWithRejectionM :: (Ord a, FiniteBits a, Num a, MonadRandom g m) => (g -> m a) -> a -> g -> m a +bitmaskWithRejectionM uniform range gen = go + where + mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) + go = do + x <- uniform gen + let x' = x .&. mask + if x' >= range + then go + else pure x' + + +bitmaskWithRejection32M :: MonadRandom g m => Word32 -> g -> m Word32 +bitmaskWithRejection32M = bitmaskWithRejectionM uniformWord32 +bitmaskWithRejection64M :: MonadRandom g m => Word64 -> g -> m Word64 +bitmaskWithRejection64M = bitmaskWithRejectionM uniformWord64 int32Count :: Integer int32Count = toInteger (maxBound::Int32) - toInteger (minBound::Int32) + 1 -- GHC ticket #3982 From 1a510718acf9f19c3cba81b963be06a801e29464 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Mon, 24 Feb 2020 02:12:03 +0300 Subject: [PATCH 010/170] Attempt to use Word64 only --- System/Random.hs | 49 +++++++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 2edfe22ee..581b75918 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -236,41 +236,30 @@ class Monad m => MonadRandom g m where uniformByteArrayPrim :: forall g m . (MonadRandom g m, PrimMonad m) => Int -> g -> m ByteArray uniformByteArrayPrim n0 gen = do let n = max 0 n0 - (n64, nrem) = n `quotRem` 8 + (n64, nrem64) = n `quotRem` 8 ma :: MutableByteArray (PrimState m) <- newByteArray n - let go :: (PrimMonad m, Prim a) => (g -> m a) -> Int -> Int -> m () - go f i k - | i < k = do - w <- f gen - writeByteArray ma i w - go f (i + 1) k + let go i k + | i < k = uniformWord64 gen >>= writeByteArray ma i >> go (i + 1) k | otherwise = return () - go uniformWord64 0 n64 - go uniformWord8 (n - nrem) n + write :: (PrimMonad m, Prim a) => a -> Int -> Int -> Int -> m (Int, Int) + write w i krem s + | krem >= s = (i + s, krem - s) <$ writeByteArray ma n64 w + | otherwise = pure (i, krem) + {-# INLINE write #-} + getChunk :: (Integral a, FiniteBits a) => Word64 -> a -> a + getChunk w64 mask = + fromIntegral ((w64 `unsafeShiftR` finiteBitSize mask) .&. fromIntegral mask) + {-# INLINE getChunk #-} + go 0 n64 + when (nrem64 > 0) $ do + w64 <- uniformWord64 gen + (n32, nrem32) <- write (getChunk w64 (maxBound :: Word32)) n64 nrem64 4 + (n16, nrem16) <- write (getChunk w64 (maxBound :: Word16)) n32 nrem32 2 + when (nrem16 == 1) $ + writeByteArray ma n16 (getChunk w64 (maxBound :: Word8)) unsafeFreezeByteArray ma {-# INLINE uniformByteArrayPrim #-} --- g = mkStdGen 217 --- genBytes 20 g --- -- TODO: benchmark this version against StateT version. --- genByteArray :: RandomGen b => Int -> b -> (ByteArray, b) --- genByteArray n0 g0 = runST $ do --- let n = max 0 n0 --- nrem = n `rem` 8 --- n64 = n - nrem --- ma <- newByteArray n --- let go64 i g | i < n64 = --- case genWord64 g of --- (w64, g') -> writeByteArray ma i w64 >> go64 (i + 1) g' --- | otherwise = pure g --- go8 i g | i < n = --- case genWord8 g of --- (w8, g') -> writeByteArray ma i w8 >> go8 (i + 1) g' --- | otherwise = pure g --- g' <- go8 n64 =<< go64 0 g0 --- a <- unsafeFreezeByteArray ma --- pure (a, g') - data SysRandom = SysRandom From 046f1e59fb5bc709384bd2a2892ade81de0aea79 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Mon, 24 Feb 2020 03:17:31 +0300 Subject: [PATCH 011/170] A working solution for genrating a ByteArray, so it is efficient and platform independent --- System/Random.hs | 63 ++++++++++++++++++++++++++++++------------------ random.cabal | 1 + 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 581b75918..697c45df7 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1,6 +1,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MagicHash #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} @@ -102,6 +103,12 @@ import Data.Word import Foreign.C.Types import qualified System.Random.MWC as MWC +import Foreign.Ptr (plusPtr) +import Foreign.Storable (peekByteOff, pokeByteOff) +import Foreign.Marshal.Alloc (alloca) +import Data.ByteString.Builder.Prim (word64LE) +import Data.ByteString.Builder.Prim.Internal (runF) + import System.CPUTime ( getCPUTime ) import Data.Time ( getCurrentTime, UTCTime(..) ) import Data.Ratio ( numerator, denominator ) @@ -115,7 +122,7 @@ import Data.IORef ( IORef, newIORef, readIORef, writeIORef, #endif import Numeric ( readDec ) -import GHC.Exts ( build ) +import GHC.Exts ( Ptr(..), build, byteArrayContents#, unsafeCoerce# ) #if !MIN_VERSION_base (4,6,0) atomicModifyIORef' :: IORef a -> (a -> (a,b)) -> IO b @@ -126,6 +133,15 @@ atomicModifyIORef' ref f = do b `seq` return b #endif +mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 +{-# INLINE mutableByteArrayContentsCompat #-} +#if !MIN_VERSION_primitive(0,7,0) +mutableByteArrayContentsCompat (MutableByteArray arr#) + = Ptr (byteArrayContents# (unsafeCoerce# arr#)) +#else +mutableByteArrayContentsCompat = mutableByteArrayContents +#endif + getTime :: IO (Integer, Integer) getTime = do utc <- getCurrentTime @@ -233,30 +249,32 @@ class Monad m => MonadRandom g m where uniformBytes = uniformByteArrayPrim {-# INLINE uniformBytes #-} -uniformByteArrayPrim :: forall g m . (MonadRandom g m, PrimMonad m) => Int -> g -> m ByteArray + +-- | This function will efficiently generate a sequence of random bytes in a platform +-- independent manner. Memory allocated will be pinned, so it is safe to use for FFI +-- calls. +uniformByteArrayPrim :: (MonadRandom g m, PrimMonad m) => Int -> g -> m ByteArray uniformByteArrayPrim n0 gen = do let n = max 0 n0 (n64, nrem64) = n `quotRem` 8 - ma :: MutableByteArray (PrimState m) <- newByteArray n - let go i k - | i < k = uniformWord64 gen >>= writeByteArray ma i >> go (i + 1) k - | otherwise = return () - write :: (PrimMonad m, Prim a) => a -> Int -> Int -> Int -> m (Int, Int) - write w i krem s - | krem >= s = (i + s, krem - s) <$ writeByteArray ma n64 w - | otherwise = pure (i, krem) - {-# INLINE write #-} - getChunk :: (Integral a, FiniteBits a) => Word64 -> a -> a - getChunk w64 mask = - fromIntegral ((w64 `unsafeShiftR` finiteBitSize mask) .&. fromIntegral mask) - {-# INLINE getChunk #-} - go 0 n64 + ma <- newPinnedByteArray n + let go i ptr + | i < n64 = do + w64 <- uniformWord64 gen + unsafeIOToPrim $ runF word64LE w64 ptr + go (i + 1) (ptr `plusPtr` 8) + | otherwise = return ptr + ptr <- go 0 (mutableByteArrayContentsCompat ma) when (nrem64 > 0) $ do w64 <- uniformWord64 gen - (n32, nrem32) <- write (getChunk w64 (maxBound :: Word32)) n64 nrem64 4 - (n16, nrem16) <- write (getChunk w64 (maxBound :: Word16)) n32 nrem32 2 - when (nrem16 == 1) $ - writeByteArray ma n16 (getChunk w64 (maxBound :: Word8)) + -- in order not to mess up the byte order we write generated Word64 into a temporary + -- pointer and then copy only the missing bytes over to the array + unsafeIOToPrim $ + alloca $ \w64ptr -> do + runF word64LE w64 w64ptr + forM_ [0 .. nrem64 - 1] $ \i -> do + w8 :: Word8 <- peekByteOff w64ptr i + pokeByteOff ptr i w8 unsafeFreezeByteArray ma {-# INLINE uniformByteArrayPrim #-} @@ -319,7 +337,7 @@ runStateTGen_ :: (RandomGen g, Functor f) => g -> StateT g f a -> f a runStateTGen_ g = fmap fst . flip runStateT g randomList :: (Random a, RandomGen g, Num a) => Int -> g -> [a] -randomList n g = runStateGen_ g $ replicateM n (genRandomR (1, 6)) +randomList n g = runStateGen_ g $ replicateM n (randomM PureGen) -- | Example: @@ -342,9 +360,6 @@ randomListM' seed n = do xs <- replicateM n (randomRM (1, 6) gen) return (gen, xs) --- someActionM :: (RandomGen g, Random a, MonadState g m, Num a) => Int -> m [a] --- someActionM n = replicateM n (genRandomR (1, 6)) - {- | The 'StdGen' instance of 'RandomGen' has a 'genRange' of at least 30 bits. diff --git a/random.cabal b/random.cabal index e8bb0b565..5d9a4035e 100644 --- a/random.cabal +++ b/random.cabal @@ -36,6 +36,7 @@ Library System.Random GHC-Options: -O2 build-depends: base >= 3 && < 5 + , bytestring , primitive , time , mtl From 44b33ea06e39f09cf9598d434f1dfb4c3bd0689c Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Tue, 25 Feb 2020 04:05:38 +0300 Subject: [PATCH 012/170] Helper functions for generating ByteString --- System/Random.hs | 78 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 13 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 697c45df7..ebd01f258 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleInstances #-} @@ -68,6 +70,7 @@ module System.Random , runStateGen_ , runStateTGen , runStateTGen_ + , runPureGenST -- ** The global random number generator @@ -81,8 +84,10 @@ module System.Random -- * Random values of various types , Random(..) - -- * Default generators + -- * Generators for sequences of bytes , uniformByteArrayPrim + , uniformByteStringPrim + , genByteString -- * References -- $references @@ -103,11 +108,14 @@ import Data.Word import Foreign.C.Types import qualified System.Random.MWC as MWC +import GHC.ForeignPtr import Foreign.Ptr (plusPtr) import Foreign.Storable (peekByteOff, pokeByteOff) import Foreign.Marshal.Alloc (alloca) import Data.ByteString.Builder.Prim (word64LE) import Data.ByteString.Builder.Prim.Internal (runF) +import Data.ByteString.Internal (ByteString(PS)) +import Data.ByteString.Short.Internal (ShortByteString(SBS), fromShort) import System.CPUTime ( getCPUTime ) import Data.Time ( getCurrentTime, UTCTime(..) ) @@ -182,9 +190,9 @@ class RandomGen g where genWord64R :: Word64 -> g -> (Word64, g) genWord64R m = randomIvalIntegral (minBound, m) - genBytes :: Int -> g -> (ByteArray, g) - genBytes n g = runST $ runStateTGen g $ uniformByteArrayPrim n PureGen - {-# INLINE genBytes #-} + genByteArray :: Int -> g -> (ByteArray, g) + genByteArray n g = runPureGenST g $ uniformByteArrayPrim n + {-# INLINE genByteArray #-} -- |The 'genRange' operation yields the range of values returned by @@ -244,10 +252,10 @@ class Monad m => MonadRandom g m where uniformWord32 = uniformWord32R maxBound uniformWord64 :: g -> m Word64 uniformWord64 = uniformWord64R maxBound - uniformBytes :: Int -> g -> m ByteArray - default uniformBytes :: PrimMonad m => Int -> g -> m ByteArray - uniformBytes = uniformByteArrayPrim - {-# INLINE uniformBytes #-} + uniformByteArray :: Int -> g -> m ByteArray + default uniformByteArray :: PrimMonad m => Int -> g -> m ByteArray + uniformByteArray = uniformByteArrayPrim + {-# INLINE uniformByteArray #-} -- | This function will efficiently generate a sequence of random bytes in a platform @@ -261,14 +269,19 @@ uniformByteArrayPrim n0 gen = do let go i ptr | i < n64 = do w64 <- uniformWord64 gen + -- Writing 8 bytes at a time in a Little-endian order gives us platform + -- portability unsafeIOToPrim $ runF word64LE w64 ptr go (i + 1) (ptr `plusPtr` 8) | otherwise = return ptr ptr <- go 0 (mutableByteArrayContentsCompat ma) when (nrem64 > 0) $ do w64 <- uniformWord64 gen - -- in order not to mess up the byte order we write generated Word64 into a temporary - -- pointer and then copy only the missing bytes over to the array + -- In order to not mess up the byte order we write generated Word64 into a temporary + -- pointer and then copy only the missing bytes over to the array. It is tempting to + -- simply generate as many bytes as we still need using smaller generators + -- (eg. uniformWord8), but that would result in inconsistent tail when total length is + -- slightly varied. unsafeIOToPrim $ alloca $ \w64ptr -> do runF word64LE w64 w64ptr @@ -279,11 +292,51 @@ uniformByteArrayPrim n0 gen = do {-# INLINE uniformByteArrayPrim #-} +pinnedMutableByteArrayToByteString :: MutableByteArray RealWorld -> ByteString +pinnedMutableByteArrayToByteString mba = + PS (pinnedMutableByteArrayToForeignPtr mba) 0 (sizeofMutableByteArray mba) +{-# INLINE pinnedMutableByteArrayToByteString #-} + +pinnedMutableByteArrayToForeignPtr :: MutableByteArray RealWorld -> ForeignPtr a +pinnedMutableByteArrayToForeignPtr mba@(MutableByteArray mba#) = + case mutableByteArrayContentsCompat mba of + Ptr addr# -> ForeignPtr addr# (PlainPtr mba#) +{-# INLINE pinnedMutableByteArrayToForeignPtr #-} + +-- | Generate a ByteString using a pure generator. For monadic counterpart see +-- `uniformByteStringPrim`. +-- +-- @since 1.2 +uniformByteStringPrim :: + (MonadRandom g m, PrimMonad m) => Int -> g -> m ByteString +uniformByteStringPrim n g = do + ba@(ByteArray ba#) <- uniformByteArray n g + if isByteArrayPinned ba + then unsafeIOToPrim $ + pinnedMutableByteArrayToByteString <$> unsafeThawByteArray ba + else return $ fromShort (SBS ba#) +{-# INLINE uniformByteStringPrim #-} + +-- | Generate a ByteString using a pure generator. For monadic counterpart see +-- `uniformByteStringPrim`. +-- +-- @since 1.2 +genByteString :: RandomGen g => Int -> g -> (ByteString, g) +genByteString n g = runPureGenST g (uniformByteStringPrim n) +{-# INLINE genByteString #-} + +-- | Run an effectful generating action in `ST` monad using a pure generator. +-- +-- @since 1.2 +runPureGenST :: RandomGen g => g -> (forall s . PureGen g -> StateT g (ST s) a) -> (a, g) +runPureGenST g action = runST $ runStateTGen g $ action PureGen +{-# INLINE runPureGenST #-} + data SysRandom = SysRandom -- Example /dev/urandom instance MonadIO m => MonadRandom SysRandom m where - uniformBytes n gen = liftIO $ uniformByteArrayPrim n gen + uniformByteArray n gen = liftIO $ uniformByteArrayPrim n gen -- Example mwc-random instance (s ~ PrimState m, PrimMonad m) => MonadRandom (MWC.Gen s) m where @@ -312,7 +365,7 @@ instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) m where uniformWord16 _ = state genWord16 uniformWord32 _ = state genWord32 uniformWord64 _ = state genWord64 - uniformBytes n _ = state (genBytes n) + uniformByteArray n _ = state (genByteArray n) genRandom :: (RandomGen g, Random a, MonadState g m) => m a genRandom = randomM PureGen @@ -393,7 +446,6 @@ data StdGen instance RandomGen StdGen where next = stdNext genRange _ = stdRange - -- ^ this is likely incorrect, but we'll switch to splitmix anyways #ifdef ENABLE_SPLITTABLEGEN instance SplittableGen StdGen where From 0b45a295d08f3342839fafeba9d58ca080ea55ef Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Tue, 25 Feb 2020 04:17:13 +0300 Subject: [PATCH 013/170] Cleanup cabal file a bit. Bump up the version to 1.2 --- random.cabal | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/random.cabal b/random.cabal index 5d9a4035e..3878c0081 100644 --- a/random.cabal +++ b/random.cabal @@ -1,7 +1,5 @@ name: random -version: 1.1 - - +version: 1.2 license: BSD3 @@ -11,16 +9,13 @@ bug-reports: https://github.com/haskell/random/issues synopsis: random number library category: System description: - This package provides a basic random number generation - library, including the ability to split random number - generators. + This package provides a basic random number generation + library, including the ability to split random number + generators. extra-source-files: - .travis.yml README.md CHANGELOG.md - .gitignore - .darcs-boring @@ -46,10 +41,6 @@ source-repository head type: git location: http://git.haskell.org/packages/random.git --- To run the Test-Suite: --- $ cabal configure --enable-tests --- $ cabal test --show-details=always --test-options="+RTS -M1M -RTS" - Test-Suite T7936 type: exitcode-stdio-1.0 main-is: T7936.hs @@ -62,13 +53,11 @@ Test-Suite TestRandomRs main-is: TestRandomRs.hs hs-source-dirs: tests build-depends: base >= 3 && < 5, random - ghc-options: -rtsopts -O2 - -- TODO. Why does the following not work? - --test-options: +RTS -M1M -RTS + ghc-options: -rtsopts -O2 -with-rtsopts=-M1M Test-Suite TestRandomIOs type: exitcode-stdio-1.0 main-is: TestRandomIOs.hs hs-source-dirs: tests build-depends: base >= 3 && < 5, random - ghc-options: -rtsopts -O2 + ghc-options: -rtsopts -O2 -with-rtsopts=-M1M From 83262ccd8a2d4b8f29b14c535b6bdf997bc7a497 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Tue, 25 Feb 2020 04:37:48 +0300 Subject: [PATCH 014/170] General cleanup, remove dead code. Remove usagae of tabs and trailing spaces --- System/Random.hs | 321 +++++++++++++++++++---------------------------- random.cabal | 2 +- 2 files changed, 128 insertions(+), 195 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index ebd01f258..512b5291b 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1,10 +1,10 @@ -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DefaultSignatures #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} #if __GLASGOW_HASKELL__ >= 701 @@ -16,7 +16,7 @@ -- Module : System.Random -- Copyright : (c) The University of Glasgow 2001 -- License : BSD-style (see the file LICENSE in the 'random' repository) --- +-- -- Maintainer : libraries@haskell.org -- Stability : stable -- Portability : portable @@ -27,7 +27,7 @@ -- or to get different results on each run by using the system-initialised -- generator or by supplying a seed from some other source. -- --- The library is split into two layers: +-- The library is split into two layers: -- -- * A core /random number generator/ provides a supply of bits. -- The class 'RandomGen' provides a common interface to such generators. @@ -47,99 +47,81 @@ ----------------------------------------------------------------------------- module System.Random - ( + ( - -- $intro + -- $intro - -- * Random number generators + -- * Random number generators -#ifdef ENABLE_SPLITTABLEGEN - RandomGen(..) - , SplittableGen(..) -#else - RandomGen(..) -#endif - , MonadRandom(..) - -- ** Standard random number generators - , StdGen - , mkStdGen + RandomGen(..) + , MonadRandom(..) + -- ** Standard random number generators + , StdGen + , mkStdGen - , genRandom - , genRandomR - , runStateGen - , runStateGen_ - , runStateTGen - , runStateTGen_ - , runPureGenST + -- * Stateful interface for pure generators + , splitGen + , genRandom + , genRandomR + , runStateGen + , runStateGen_ + , runStateTGen + , runStateTGen_ + , runPureGenST - -- ** The global random number generator + -- ** The global random number generator - -- $globalrng + -- $globalrng - , getStdRandom - , getStdGen - , setStdGen - , newStdGen + , getStdRandom + , getStdGen + , setStdGen + , newStdGen - -- * Random values of various types - , Random(..) + -- * Random values of various types + , Random(..) - -- * Generators for sequences of bytes - , uniformByteArrayPrim - , uniformByteStringPrim - , genByteString + -- * Generators for sequences of bytes + , uniformByteArrayPrim + , uniformByteStringPrim + , genByteString - -- * References - -- $references + -- * References + -- $references - ) where + ) where import Prelude import Control.Arrow -import Control.Monad.ST import Control.Monad.Primitive +import Control.Monad.ST import Control.Monad.State.Strict import Data.Bits import Data.Int -import Data.Primitive.Types (Prim) import Data.Primitive.ByteArray import Data.Word import Foreign.C.Types -import qualified System.Random.MWC as MWC -import GHC.ForeignPtr -import Foreign.Ptr (plusPtr) -import Foreign.Storable (peekByteOff, pokeByteOff) -import Foreign.Marshal.Alloc (alloca) import Data.ByteString.Builder.Prim (word64LE) import Data.ByteString.Builder.Prim.Internal (runF) import Data.ByteString.Internal (ByteString(PS)) import Data.ByteString.Short.Internal (ShortByteString(SBS), fromShort) +import Foreign.Marshal.Alloc (alloca) +import Foreign.Ptr (plusPtr) +import Foreign.Storable (peekByteOff, pokeByteOff) +import GHC.ForeignPtr -import System.CPUTime ( getCPUTime ) -import Data.Time ( getCurrentTime, UTCTime(..) ) -import Data.Ratio ( numerator, denominator ) -import Data.Char ( isSpace, chr, ord ) -import System.IO.Unsafe ( unsafePerformIO ) -import Data.IORef ( IORef, newIORef, readIORef, writeIORef, -#if MIN_VERSION_base (4,6,0) - atomicModifyIORef' ) -#else - atomicModifyIORef ) -#endif -import Numeric ( readDec ) +import Data.Char (chr, isSpace, ord) +import Data.IORef (IORef, newIORef, readIORef, writeIORef, atomicModifyIORef') +import Data.Ratio (denominator, numerator) +import Data.Time (UTCTime(..), getCurrentTime) +import System.CPUTime (getCPUTime) +import System.IO.Unsafe (unsafePerformIO) -import GHC.Exts ( Ptr(..), build, byteArrayContents#, unsafeCoerce# ) +import GHC.Exts (Ptr(..), build, byteArrayContents#, unsafeCoerce#) +import Numeric (readDec) -#if !MIN_VERSION_base (4,6,0) -atomicModifyIORef' :: IORef a -> (a -> (a,b)) -> IO b -atomicModifyIORef' ref f = do - b <- atomicModifyIORef ref - (\x -> let (a, b) = f x - in (a, a `seq` b)) - b `seq` return b -#endif mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 {-# INLINE mutableByteArrayContentsCompat #-} @@ -159,12 +141,6 @@ getTime = do -- | The class 'RandomGen' provides a common interface to random number -- generators. -- -#ifdef ENABLE_SPLITTABLEGEN --- Minimal complete definition: 'next'. -#else --- Minimal complete definition: 'next' and 'split'. -#endif - class RandomGen g where -- |The 'next' operation returns an 'Int' that is uniformly distributed -- in the range returned by 'genRange' (including both end points), @@ -217,11 +193,6 @@ class RandomGen g where -- default method genRange _ = (minBound, maxBound) -#ifdef ENABLE_SPLITTABLEGEN --- | The class 'SplittableGen' proivides a way to specify a random number --- generator that can be split into two new generators. -class SplittableGen g where -#endif -- |The 'split' operation allows one to obtain two distinct random number -- generators. This is very useful in functional programs (for example, when -- passing a random number generator down to recursive calls), but very @@ -332,27 +303,8 @@ runPureGenST :: RandomGen g => g -> (forall s . PureGen g -> StateT g (ST s) a) runPureGenST g action = runST $ runStateTGen g $ action PureGen {-# INLINE runPureGenST #-} -data SysRandom = SysRandom - --- Example /dev/urandom -instance MonadIO m => MonadRandom SysRandom m where - uniformByteArray n gen = liftIO $ uniformByteArrayPrim n gen - --- Example mwc-random -instance (s ~ PrimState m, PrimMonad m) => MonadRandom (MWC.Gen s) m where - type Seed (MWC.Gen s) = MWC.Seed - restore = MWC.restore - save = MWC.save - uniformWord32R u = MWC.uniformR (0, u) - uniformWord64R u = MWC.uniformR (0, u) - uniformWord8 = MWC.uniform - uniformWord16 = MWC.uniform - {-# INLINE uniformWord32 #-} - uniformWord32 = MWC.uniform - {-# INLINE uniformWord64 #-} - uniformWord64 = MWC.uniform - --- | An opaque data type that carries the state of a pure generator at the type level + +-- | An opaque data type that carries the type of a pure generator data PureGen g = PureGen instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) m where @@ -367,13 +319,21 @@ instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) m where uniformWord64 _ = state genWord64 uniformByteArray n _ = state (genByteArray n) +-- | Generate a random value in a state monad +-- +-- @since 1.2 genRandom :: (RandomGen g, Random a, MonadState g m) => m a genRandom = randomM PureGen +-- | Generate a random value within a range in a state monad +-- +-- @since 1.2 genRandomR :: (RandomGen g, Random a, MonadState g m) => (a, a) -> m a genRandomR r = randomRM r PureGen -- | Split current generator and update the state with one part, while returning the other. +-- +-- @since 1.2 splitGen :: (MonadState g m, RandomGen g) => m g splitGen = state split @@ -389,30 +349,6 @@ runStateTGen = flip runStateT runStateTGen_ :: (RandomGen g, Functor f) => g -> StateT g f a -> f a runStateTGen_ g = fmap fst . flip runStateT g -randomList :: (Random a, RandomGen g, Num a) => Int -> g -> [a] -randomList n g = runStateGen_ g $ replicateM n (randomM PureGen) - - --- | Example: --- --- λ> runStateGen_ (mkGen 217 :: StdGen) (randomListM PureGen 10) :: [Word64] --- [1,2,3,5,5,2,5,1,4,1] -randomListM :: (Random a, MonadRandom g m, Num a) => g -> Int -> m [a] -randomListM gen n = replicateM n (randomRM (1, 6) gen) - -rlist :: Int -> ([Word64], [Word64]) -rlist n = (xs, ys) - where - xs = runStateGen_ (mkStdGen 217 :: StdGen) (randomListM PureGen n) :: [Word64] - ys = runST $ MWC.create >>= (`randomListM` n) - - -randomListM' :: MonadRandom g m => Seed g -> Int -> m (g, [Word64]) -randomListM' seed n = do - gen <- restore seed - xs <- replicateM n (randomRM (1, 6) gen) - return (gen, xs) - {- | The 'StdGen' instance of 'RandomGen' has a 'genRange' of at least 30 bits. @@ -430,31 +366,28 @@ It is required that @'read' ('show' g) == g@. In addition, 'reads' may be used to map an arbitrary string (not necessarily one produced by 'show') onto a value of type 'StdGen'. In general, the 'Read' -instance of 'StdGen' has the following properties: +instance of 'StdGen' has the following properties: -* It guarantees to succeed on any string. +* It guarantees to succeed on any string. -* It guarantees to consume only a finite portion of the string. +* It guarantees to consume only a finite portion of the string. * Different argument strings are likely to result in different results. -} -data StdGen +data StdGen = StdGen !Int32 !Int32 instance RandomGen StdGen where next = stdNext genRange _ = stdRange -#ifdef ENABLE_SPLITTABLEGEN -instance SplittableGen StdGen where -#endif split = stdSplit instance Show StdGen where - showsPrec p (StdGen s1 s2) = - showsPrec p s1 . + showsPrec p (StdGen s1 s2) = + showsPrec p s1 . showChar ' ' . showsPrec p s2 @@ -462,12 +395,12 @@ instance Read StdGen where readsPrec _p = \ r -> case try_read r of r'@[_] -> r' - _ -> [stdFromString r] -- because it shouldn't ever fail. - where + _ -> [stdFromString r] -- because it shouldn't ever fail. + where try_read r = do (s1, r1) <- readDec (dropWhile isSpace r) - (s2, r2) <- readDec (dropWhile isSpace r1) - return (StdGen s1 s2, r2) + (s2, r2) <- readDec (dropWhile isSpace r1) + return (StdGen s1 s2, r2) {- If we cannot unravel the StdGen from a string, create @@ -475,8 +408,8 @@ instance Read StdGen where -} stdFromString :: String -> (StdGen, String) stdFromString s = (mkStdGen num, rest) - where (cs, rest) = splitAt 6 s - num = foldl (\a x -> x + 3 * a) 1 (map ord cs) + where (cs, rest) = splitAt 6 s + num = foldl (\a x -> x + 3 * a) 1 (map ord cs) {- | @@ -494,12 +427,12 @@ respectively." -} mkStdGen32 :: Int32 -> StdGen mkStdGen32 sMaybeNegative = StdGen (s1+1) (s2+1) - where - -- We want a non-negative number, but we can't just take the abs - -- of sMaybeNegative as -minBound == minBound. - s = sMaybeNegative .&. maxBound - (q, s1) = s `divMod` 2147483562 - s2 = q `mod` 2147483398 + where + -- We want a non-negative number, but we can't just take the abs + -- of sMaybeNegative as -minBound == minBound. + s = sMaybeNegative .&. maxBound + (q, s1) = s `divMod` 2147483562 + s2 = q `mod` 2147483398 createStdGen :: Integer -> StdGen createStdGen s = mkStdGen32 $ fromIntegral s @@ -561,7 +494,7 @@ class Random a where -- | A variant of 'random' that uses the global random number generator -- (see "System.Random#globalrng"). randomIO :: IO a - randomIO = getStdRandom random + randomIO = getStdRandom random -- | Produce an infinite list-equivalent of random values. {-# INLINE buildRandoms #-} @@ -578,7 +511,7 @@ buildRandoms cons rand = go instance Random Integer where randomR ival g = randomIvalInteger ival g - random g = randomR (toInteger (minBound::Int), toInteger (maxBound::Int)) g + random g = randomR (toInteger (minBound::Int), toInteger (maxBound::Int)) g instance Random Int8 where randomR = bitmaskWithRejection @@ -757,13 +690,13 @@ instance Random CUIntMax where randomRM (CUIntMax b, CUIntMax t) = fmap CUIntMax . randomRM (b, t) instance Random Char where - randomR (a,b) g = + randomR (a,b) g = case (randomIvalInteger (toInteger (ord a), toInteger (ord b)) g) of (x,g') -> (chr x, g') - random g = randomR (minBound,maxBound) g + random g = randomR (minBound,maxBound) g instance Random Bool where - randomR (a,b) g = + randomR (a,b) g = case (randomIvalInteger (bool2Int a, bool2Int b) g) of (x, g') -> (int2Bool x, g') where @@ -771,18 +704,18 @@ instance Random Bool where bool2Int False = 0 bool2Int True = 1 - int2Bool :: Int -> Bool - int2Bool 0 = False - int2Bool _ = True + int2Bool :: Int -> Bool + int2Bool 0 = False + int2Bool _ = True - random g = randomR (minBound,maxBound) g + random g = randomR (minBound,maxBound) g {-# INLINE randomRFloating #-} randomRFloating :: (Fractional a, Num a, Ord a, Random a, RandomGen g) => (a, a) -> g -> (a, g) -randomRFloating (l,h) g +randomRFloating (l,h) g | l>h = randomRFloating (h,l) g - | otherwise = let (coef,g') = random g in - (2.0 * (0.5*l + coef * (0.5*h - 0.5*l)), g') -- avoid overflow + | otherwise = let (coef,g') = random g in + (2.0 * (0.5*l + coef * (0.5*h - 0.5*l)), g') -- avoid overflow instance Random Double where randomR = randomRFloating @@ -790,12 +723,12 @@ instance Random Double where randomDouble :: RandomGen b => b -> (Double, b) randomDouble rng = - case random rng of - (x,rng') -> + case random rng of + (x,rng') -> -- We use 53 bits of randomness corresponding to the 53 bit significand: - ((fromIntegral (mask53 .&. (x::Int64)) :: Double) - / fromIntegral twoto53, rng') - where + ((fromIntegral (mask53 .&. (x::Int64)) :: Double) + / fromIntegral twoto53, rng') + where twoto53 = (2::Int64) ^ (53::Int64) mask53 = twoto53 - 1 @@ -806,14 +739,14 @@ instance Random Float where randomFloat :: RandomGen b => b -> (Float, b) randomFloat rng = - -- TODO: Faster to just use 'next' IF it generates enough bits of randomness. - case random rng of - (x,rng') -> + -- TODO: Faster to just use 'next' IF it generates enough bits of randomness. + case random rng of + (x,rng') -> -- We use 24 bits of randomness corresponding to the 24 bit significand: - ((fromIntegral (mask24 .&. (x::Int32)) :: Float) - / fromIntegral twoto24, rng') - -- Note, encodeFloat is another option, but I'm not seeing slightly - -- worse performance with the following [2011.06.25]: + ((fromIntegral (mask24 .&. (x::Int32)) :: Float) + / fromIntegral twoto24, rng') + -- Note, encodeFloat is another option, but I'm not seeing slightly + -- worse performance with the following [2011.06.25]: -- (encodeFloat rand (-24), rng') where mask24 = twoto24 - 1 @@ -822,8 +755,8 @@ randomFloat rng = -- CFloat/CDouble are basically the same as a Float/Double: -- instance Random CFloat where -- randomR = randomRFloating - -- random rng = case random rng of - -- (x,rng') -> (realToFrac (x::Float), rng') + -- random rng = case random rng of + -- (x,rng') -> (realToFrac (x::Float), rng') -- instance Random CDouble where -- randomR = randomRFloating @@ -831,8 +764,8 @@ randomFloat rng = -- -- Presently, this is showing better performance than the Double instance: -- -- (And yet, if the Double instance uses randomFrac then its performance is much worse!) -- random = randomFrac --- -- random rng = case random rng of --- -- (x,rng') -> (realToFrac (x::Double), rng') +-- -- random rng = case random rng of +-- -- (x,rng') -> (realToFrac (x::Double), rng') mkStdRNG :: Integer -> IO StdGen mkStdRNG o = do @@ -849,7 +782,7 @@ randomIvalIntegral (l,h) = randomIvalInteger (toInteger l, toInteger h) {-# SPECIALIZE randomIvalInteger :: (Num a) => (Integer, Integer) -> StdGen -> (a, StdGen) #-} - + randomIvalInteger :: (RandomGen g, Num a) => (Integer, Integer) -> g -> (a, g) randomIvalInteger (l,h) rng | l > h = randomIvalInteger (h,l) rng @@ -868,7 +801,7 @@ randomIvalInteger (l,h) rng k = h - l + 1 magtgt = k * q - -- generate random values until we exceed the target magnitude + -- generate random values until we exceed the target magnitude f mag v g | mag >= magtgt = (v, g) | otherwise = v' `seq`f (mag*b) v' g' where (x,g') = next g @@ -880,18 +813,18 @@ randomFrac :: (RandomGen g, Fractional a) => g -> (a, g) randomFrac = randomIvalDouble (0::Double,1) realToFrac randomIvalDouble :: (RandomGen g, Fractional a) => (Double, Double) -> (Double -> a) -> g -> (a, g) -randomIvalDouble (l,h) fromDouble rng +randomIvalDouble (l,h) fromDouble rng | l > h = randomIvalDouble (h,l) fromDouble rng - | otherwise = + | otherwise = case (randomIvalInteger (toInteger (minBound::Int32), toInteger (maxBound::Int32)) rng) of - (x, rng') -> - let - scaled_x = - fromDouble (0.5*l + 0.5*h) + -- previously (l+h)/2, overflowed - fromDouble ((0.5*h - 0.5*l) / (0.5 * realToFrac int32Count)) * -- avoid overflow - fromIntegral (x::Int32) - in - (scaled_x, rng') + (x, rng') -> + let + scaled_x = + fromDouble (0.5*l + 0.5*h) + -- previously (l+h)/2, overflowed + fromDouble ((0.5*h - 0.5*l) / (0.5 * realToFrac int32Count)) * -- avoid overflow + fromIntegral (x::Int32) + in + (scaled_x, rng') bitmaskWithRejection :: @@ -963,16 +896,16 @@ stdRange = (1, 2147483562) stdNext :: StdGen -> (Int, StdGen) -- Returns values in the range stdRange stdNext (StdGen s1 s2) = (fromIntegral z', StdGen s1'' s2'') - where z' = if z < 1 then z + 2147483562 else z - z = s1'' - s2'' - - k = s1 `quot` 53668 - s1' = 40014 * (s1 - k * 53668) - k * 12211 - s1'' = if s1' < 0 then s1' + 2147483563 else s1' - - k' = s2 `quot` 52774 - s2' = 40692 * (s2 - k' * 52774) - k' * 3791 - s2'' = if s2' < 0 then s2' + 2147483399 else s2' + where z' = if z < 1 then z + 2147483562 else z + z = s1'' - s2'' + + k = s1 `quot` 53668 + s1' = 40014 * (s1 - k * 53668) - k * 12211 + s1'' = if s1' < 0 then s1' + 2147483563 else s1' + + k' = s2 `quot` 52774 + s2' = 40692 * (s2 - k' * 52774) - k' * 3791 + s2'' = if s2' < 0 then s2' + 2147483399 else s2' stdSplit :: StdGen -> (StdGen, StdGen) stdSplit std@(StdGen s1 s2) diff --git a/random.cabal b/random.cabal index 3878c0081..e51281427 100644 --- a/random.cabal +++ b/random.cabal @@ -29,7 +29,7 @@ cabal-version: >= 1.8 Library exposed-modules: System.Random - GHC-Options: -O2 + ghc-options: -Wall build-depends: base >= 3 && < 5 , bytestring , primitive From b01074feba623ab327a7d3d9ab014b9db937e379 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sat, 29 Feb 2020 18:25:58 +0300 Subject: [PATCH 015/170] Implement Uniform and UniformRange --- System/Random.hs | 417 +++++++++++++++++++++++++---------------------- 1 file changed, 221 insertions(+), 196 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 512b5291b..db78fd69c 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -62,7 +62,6 @@ module System.Random -- * Stateful interface for pure generators , splitGen , genRandom - , genRandomR , runStateGen , runStateGen_ , runStateTGen @@ -325,12 +324,6 @@ instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) m where genRandom :: (RandomGen g, Random a, MonadState g m) => m a genRandom = randomM PureGen --- | Generate a random value within a range in a state monad --- --- @since 1.2 -genRandomR :: (RandomGen g, Random a, MonadState g m) => (a, a) -> m a -genRandomR r = randomRM r PureGen - -- | Split current generator and update the state with one part, while returning the other. -- -- @since 1.2 @@ -445,11 +438,18 @@ Minimal complete definition: 'randomR' and 'random'. -} -class Random a where - randomRM :: MonadRandom g m => (a, a) -> g -> m a - randomM :: MonadRandom g m => g -> m a +class Uniform a where + uniform :: MonadRandom g m => g -> m a +class UniformRange a where + uniformR :: MonadRandom g m => (a, a) -> g -> m a + + +{-# DEPRECATED randomR "In favor of `uniformR`" #-} +{-# DEPRECATED randomRIO "In favor of `uniformR`" #-} +{-# DEPRECATED randomIO "In favor of `uniformR`" #-} +class Random a where -- | Takes a range /(lo,hi)/ and a random number generator -- /g/, and returns a random value uniformly distributed in the closed @@ -459,7 +459,8 @@ class Random a where -- depending on the implementation and the interval. {-# INLINE randomR #-} randomR :: RandomGen g => (a, a) -> g -> (a, g) - randomR r g = runStateGen g (genRandomR r) + default randomR :: (RandomGen g, UniformRange a) => (a, a) -> g -> (a, g) + randomR r g = runStateGen g (uniformR r PureGen) -- | The same as 'randomR', but using a default range determined by the type: -- @@ -474,6 +475,11 @@ class Random a where random :: RandomGen g => g -> (a, g) random g = runStateGen g genRandom + {-# INLINE randomM #-} + randomM :: MonadRandom g m => g -> m a + default randomM :: (MonadRandom g m, Uniform a) => g -> m a + randomM = uniform + -- | Plural variant of 'randomR', producing an infinite list of -- random values instead of returning a new generator. {-# INLINE randomRs #-} @@ -510,205 +516,218 @@ buildRandoms cons rand = go instance Random Integer where - randomR ival g = randomIvalInteger ival g random g = randomR (toInteger (minBound::Int), toInteger (maxBound::Int)) g + randomM g = uniformR (toInteger (minBound::Int), toInteger (maxBound::Int)) g -instance Random Int8 where - randomR = bitmaskWithRejection - random = first (fromIntegral :: Word8 -> Int8) . genWord8 - randomM = fmap (fromIntegral :: Word8 -> Int8) . uniformWord8 - randomRM = bitmaskWithRejectionRM -instance Random Int16 where - randomR = bitmaskWithRejection - random = first (fromIntegral :: Word16 -> Int16) . genWord16 - randomM = fmap (fromIntegral :: Word16 -> Int16) . uniformWord16 - randomRM = bitmaskWithRejectionRM -instance Random Int32 where - randomR = bitmaskWithRejection - random = first (fromIntegral :: Word32 -> Int32) . genWord32 - randomM = fmap (fromIntegral :: Word32 -> Int32) . uniformWord32 - randomRM = bitmaskWithRejectionRM -instance Random Int64 where - randomR = bitmaskWithRejection - random = first (fromIntegral :: Word64 -> Int64) . genWord64 - randomM = fmap (fromIntegral :: Word64 -> Int64) . uniformWord64 - randomRM = bitmaskWithRejectionRM +instance UniformRange Integer where + --uniformR ival g = randomIvalInteger ival g -- FIXME + +instance Random Int8 +instance Uniform Int8 where + uniform = fmap (fromIntegral :: Word8 -> Int8) . uniformWord8 +instance UniformRange Int8 where + +instance Random Int16 +instance Uniform Int16 where + uniform = fmap (fromIntegral :: Word16 -> Int16) . uniformWord16 +instance UniformRange Int16 where + +instance Random Int32 +instance Uniform Int32 where + uniform = fmap (fromIntegral :: Word32 -> Int32) . uniformWord32 +instance UniformRange Int32 where + +instance Random Int64 +instance Uniform Int64 where + uniform = fmap (fromIntegral :: Word64 -> Int64) . uniformWord64 +instance UniformRange Int64 where instance Random Int where randomR = bitmaskWithRejection - randomRM = bitmaskWithRejectionRM +instance Uniform Int where #if WORD_SIZE_IN_BITS < 64 - random = first (fromIntegral :: Word32 -> Int) . genWord32 - randomM = fmap (fromIntegral :: Word32 -> Int) . uniformWord32 + uniform = fmap (fromIntegral :: Word32 -> Int) . uniformWord32 #else - random = first (fromIntegral :: Word64 -> Int) . genWord64 - randomM = fmap (fromIntegral :: Word64 -> Int) . uniformWord64 + uniform = fmap (fromIntegral :: Word64 -> Int) . uniformWord64 #endif +instance UniformRange Int where -instance Random Word where - randomR = bitmaskWithRejection - randomRM = bitmaskWithRejectionRM +instance Random Word +instance Uniform Word where #if WORD_SIZE_IN_BITS < 64 - random = first (fromIntegral :: Word32 -> Word) . genWord32 - randomM = fmap (fromIntegral :: Word32 -> Word) . uniformWord32 + uniform = fmap (fromIntegral :: Word32 -> Word) . uniformWord32 #else - random = first (fromIntegral :: Word64 -> Word) . genWord64 - randomM = fmap (fromIntegral :: Word64 -> Word) . uniformWord64 + uniform = fmap (fromIntegral :: Word64 -> Word) . uniformWord64 #endif - -instance Random Word8 where - {-# INLINE randomR #-} - randomR = bitmaskWithRejection - {-# INLINE random #-} - random = genWord8 - {-# INLINE randomRM #-} - randomRM = bitmaskWithRejectionRM - {-# INLINE randomM #-} - randomM = uniformWord8 -instance Random Word16 where - {-# INLINE randomR #-} - randomR = bitmaskWithRejection - {-# INLINE random #-} - random = genWord16 - {-# INLINE randomRM #-} - randomRM = bitmaskWithRejectionRM - {-# INLINE randomM #-} - randomM = uniformWord16 -instance Random Word32 where - {-# INLINE randomR #-} - randomR = bitmaskWithRejection - {-# INLINE random #-} - random = genWord32 - {-# INLINE randomM #-} - randomM = uniformWord32 - {-# INLINE randomRM #-} - randomRM = bitmaskWithRejectionRM -instance Random Word64 where - {-# INLINE randomR #-} - randomR = bitmaskWithRejection - {-# INLINE random #-} - random = genWord64 - {-# INLINE randomM #-} - randomM = uniformWord64 - {-# INLINE randomRM #-} - randomRM = bitmaskWithRejectionRM - -instance Random CChar where - randomR (CChar b, CChar t) = first CChar . randomR (b, t) - random = first CChar . random - randomM = fmap CChar . randomM - randomRM (CChar b, CChar t) = fmap CChar . randomRM (b, t) -instance Random CSChar where - randomR (CSChar b, CSChar t) = first CSChar . randomR (b, t) - random = first CSChar . random - randomM = fmap CSChar . randomM - randomRM (CSChar b, CSChar t) = fmap CSChar . randomRM (b, t) -instance Random CUChar where - randomR (CUChar b, CUChar t) = first CUChar . randomR (b, t) - random = first CUChar . random - randomM = fmap CUChar . randomM - randomRM (CUChar b, CUChar t) = fmap CUChar . randomRM (b, t) -instance Random CShort where - randomR (CShort b, CShort t) = first CShort . randomR (b, t) - random = first CShort . random - randomM = fmap CShort . randomM - randomRM (CShort b, CShort t) = fmap CShort . randomRM (b, t) -instance Random CUShort where - randomR (CUShort b, CUShort t) = first CUShort . randomR (b, t) - random = first CUShort . random - randomM = fmap CUShort . randomM - randomRM (CUShort b, CUShort t) = fmap CUShort . randomRM (b, t) -instance Random CInt where - randomR (CInt b, CInt t) = first CInt . randomR (b, t) - random = first CInt . random - randomM = fmap CInt . randomM - randomRM (CInt b, CInt t) = fmap CInt . randomRM (b, t) -instance Random CUInt where - randomR (CUInt b, CUInt t) = first CUInt . randomR (b, t) - random = first CUInt . random - randomM = fmap CUInt . randomM - randomRM (CUInt b, CUInt t) = fmap CUInt . randomRM (b, t) -instance Random CLong where - randomR (CLong b, CLong t) = first CLong . randomR (b, t) - random = first CLong . random - randomM = fmap CLong . randomM - randomRM (CLong b, CLong t) = fmap CLong . randomRM (b, t) -instance Random CULong where - randomR (CULong b, CULong t) = first CULong . randomR (b, t) - random = first CULong . random - randomM = fmap CULong . randomM - randomRM (CULong b, CULong t) = fmap CULong . randomRM (b, t) -instance Random CPtrdiff where - randomR (CPtrdiff b, CPtrdiff t) = first CPtrdiff . randomR (b, t) - random = first CPtrdiff . random - randomM = fmap CPtrdiff . randomM - randomRM (CPtrdiff b, CPtrdiff t) = fmap CPtrdiff . randomRM (b, t) -instance Random CSize where - randomR (CSize b, CSize t) = first CSize . randomR (b, t) - random = first CSize . random - randomM = fmap CSize . randomM - randomRM (CSize b, CSize t) = fmap CSize . randomRM (b, t) -instance Random CWchar where - randomR (CWchar b, CWchar t) = first CWchar . randomR (b, t) - random = first CWchar . random - randomM = fmap CWchar . randomM - randomRM (CWchar b, CWchar t) = fmap CWchar . randomRM (b, t) -instance Random CSigAtomic where - randomR (CSigAtomic b, CSigAtomic t) = first CSigAtomic . randomR (b, t) - random = first CSigAtomic . random - randomM = fmap CSigAtomic . randomM - randomRM (CSigAtomic b, CSigAtomic t) = fmap CSigAtomic . randomRM (b, t) -instance Random CLLong where - randomR (CLLong b, CLLong t) = first CLLong . randomR (b, t) - random = first CLLong . random - randomM = fmap CLLong . randomM - randomRM (CLLong b, CLLong t) = fmap CLLong . randomRM (b, t) -instance Random CULLong where - randomR (CULLong b, CULLong t) = first CULLong . randomR (b, t) - random = first CULLong . random - randomM = fmap CULLong . randomM - randomRM (CULLong b, CULLong t) = fmap CULLong . randomRM (b, t) -instance Random CIntPtr where - randomR (CIntPtr b, CIntPtr t) = first CIntPtr . randomR (b, t) - random = first CIntPtr . random - randomM = fmap CIntPtr . randomM - randomRM (CIntPtr b, CIntPtr t) = fmap CIntPtr . randomRM (b, t) -instance Random CUIntPtr where - randomR (CUIntPtr b, CUIntPtr t) = first CUIntPtr . randomR (b, t) - random = first CUIntPtr . random - randomM = fmap CUIntPtr . randomM - randomRM (CUIntPtr b, CUIntPtr t) = fmap CUIntPtr . randomRM (b, t) -instance Random CIntMax where - randomR (CIntMax b, CIntMax t) = first CIntMax . randomR (b, t) - random = first CIntMax . random - randomM = fmap CIntMax . randomM - randomRM (CIntMax b, CIntMax t) = fmap CIntMax . randomRM (b, t) -instance Random CUIntMax where - randomR (CUIntMax b, CUIntMax t) = first CUIntMax . randomR (b, t) - random = first CUIntMax . random - randomM = fmap CUIntMax . randomM - randomRM (CUIntMax b, CUIntMax t) = fmap CUIntMax . randomRM (b, t) - -instance Random Char where - randomR (a,b) g = - case (randomIvalInteger (toInteger (ord a), toInteger (ord b)) g) of - (x,g') -> (chr x, g') - random g = randomR (minBound,maxBound) g +instance UniformRange Word where + {-# INLINE uniformR #-} + uniformR = bitmaskWithRejectionRM + +instance Random Word8 +instance Uniform Word8 where + {-# INLINE uniform #-} + uniform = uniformWord8 +instance UniformRange Word8 where + {-# INLINE uniformR #-} + uniformR = bitmaskWithRejectionRM + +instance Random Word16 +instance Uniform Word16 where + {-# INLINE uniform #-} + uniform = uniformWord16 +instance UniformRange Word16 where + {-# INLINE uniformR #-} + uniformR = bitmaskWithRejectionRM + +instance Random Word32 +instance Uniform Word32 where + {-# INLINE uniform #-} + uniform = uniformWord32 +instance UniformRange Word32 where + {-# INLINE uniformR #-} + uniformR = bitmaskWithRejectionRM + +instance Random Word64 +instance Uniform Word64 where + {-# INLINE uniform #-} + uniform = uniformWord64 +instance UniformRange Word64 where + {-# INLINE uniformR #-} + uniformR = bitmaskWithRejectionRM + + +instance Random CChar +instance Uniform CChar where + uniform = fmap CChar . uniform +instance UniformRange CChar where + uniformR (CChar b, CChar t) = fmap CChar . uniformR (b, t) + +instance Random CSChar +instance Uniform CSChar where + uniform = fmap CSChar . uniform +instance UniformRange CSChar where + uniformR (CSChar b, CSChar t) = fmap CSChar . uniformR (b, t) + +instance Random CUChar +instance Uniform CUChar where + uniform = fmap CUChar . uniform +instance UniformRange CUChar where + uniformR (CUChar b, CUChar t) = fmap CUChar . uniformR (b, t) + +instance Random CShort +instance Uniform CShort where + uniform = fmap CShort . uniform +instance UniformRange CShort where + uniformR (CShort b, CShort t) = fmap CShort . uniformR (b, t) + +instance Random CUShort +instance Uniform CUShort where + uniform = fmap CUShort . uniform +instance UniformRange CUShort where + uniformR (CUShort b, CUShort t) = fmap CUShort . uniformR (b, t) + +instance Random CInt +instance Uniform CInt where + uniform = fmap CInt . uniform +instance UniformRange CInt where + uniformR (CInt b, CInt t) = fmap CInt . uniformR (b, t) + +instance Random CUInt +instance Uniform CUInt where + uniform = fmap CUInt . uniform +instance UniformRange CUInt where + uniformR (CUInt b, CUInt t) = fmap CUInt . uniformR (b, t) + +instance Random CLong +instance Uniform CLong where + uniform = fmap CLong . uniform +instance UniformRange CLong where + uniformR (CLong b, CLong t) = fmap CLong . uniformR (b, t) + +instance Random CULong +instance Uniform CULong where + uniform = fmap CULong . uniform +instance UniformRange CULong where + uniformR (CULong b, CULong t) = fmap CULong . uniformR (b, t) + +instance Random CPtrdiff +instance Uniform CPtrdiff where + uniform = fmap CPtrdiff . uniform +instance UniformRange CPtrdiff where + uniformR (CPtrdiff b, CPtrdiff t) = fmap CPtrdiff . uniformR (b, t) + +instance Random CSize +instance Uniform CSize where + uniform = fmap CSize . uniform +instance UniformRange CSize where + uniformR (CSize b, CSize t) = fmap CSize . uniformR (b, t) + +instance Random CWchar +instance Uniform CWchar where + uniform = fmap CWchar . uniform +instance UniformRange CWchar where + uniformR (CWchar b, CWchar t) = fmap CWchar . uniformR (b, t) + +instance Random CSigAtomic +instance Uniform CSigAtomic where + uniform = fmap CSigAtomic . uniform +instance UniformRange CSigAtomic where + uniformR (CSigAtomic b, CSigAtomic t) = fmap CSigAtomic . uniformR (b, t) + +instance Random CLLong +instance Uniform CLLong where + uniform = fmap CLLong . uniform +instance UniformRange CLLong where + uniformR (CLLong b, CLLong t) = fmap CLLong . uniformR (b, t) + +instance Random CULLong +instance Uniform CULLong where + uniform = fmap CULLong . uniform +instance UniformRange CULLong where + uniformR (CULLong b, CULLong t) = fmap CULLong . uniformR (b, t) + +instance Random CIntPtr +instance Uniform CIntPtr where + uniform = fmap CIntPtr . uniform +instance UniformRange CIntPtr where + uniformR (CIntPtr b, CIntPtr t) = fmap CIntPtr . uniformR (b, t) + +instance Random CUIntPtr +instance Uniform CUIntPtr where + uniform = fmap CUIntPtr . uniform +instance UniformRange CUIntPtr where + uniformR (CUIntPtr b, CUIntPtr t) = fmap CUIntPtr . uniformR (b, t) + +instance Random CIntMax +instance Uniform CIntMax where + uniform = fmap CIntMax . uniform +instance UniformRange CIntMax where + uniformR (CIntMax b, CIntMax t) = fmap CIntMax . uniformR (b, t) + +instance Random CUIntMax +instance Uniform CUIntMax where + uniform = fmap CUIntMax . uniform +instance UniformRange CUIntMax where + uniformR (CUIntMax b, CUIntMax t) = fmap CUIntMax . uniformR (b, t) + +instance Random Char +instance Uniform Char where + uniform = uniformR (minBound, maxBound) +instance UniformRange Char where + -- FIXME instance Random Bool where - randomR (a,b) g = - case (randomIvalInteger (bool2Int a, bool2Int b) g) of - (x, g') -> (int2Bool x, g') - where - bool2Int :: Bool -> Integer - bool2Int False = 0 - bool2Int True = 1 - - int2Bool :: Int -> Bool - int2Bool 0 = False - int2Bool _ = True - - random g = randomR (minBound,maxBound) g +instance Uniform Bool where + uniform = uniformR (minBound, maxBound) +instance UniformRange Bool where + uniformR (a, b) g = int2Bool <$> uniformR (bool2Int a, bool2Int b) g + where + bool2Int :: Bool -> Int + bool2Int False = 0 + bool2Int True = 1 + int2Bool :: Int -> Bool + int2Bool 0 = False + int2Bool _ = True {-# INLINE randomRFloating #-} randomRFloating :: (Fractional a, Num a, Ord a, Random a, RandomGen g) => (a, a) -> g -> (a, g) @@ -720,6 +739,9 @@ randomRFloating (l,h) g instance Random Double where randomR = randomRFloating random = randomDouble + randomM = uniformR (0, 1) + +instance UniformRange Double randomDouble :: RandomGen b => b -> (Double, b) randomDouble rng = @@ -736,6 +758,9 @@ randomDouble rng = instance Random Float where randomR = randomRFloating random = randomFloat + randomM = uniformR (0, 1) + +instance UniformRange Float randomFloat :: RandomGen b => b -> (Float, b) randomFloat rng = From 028de7ae44086dc989eebdf53d8d5b5d52102236 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Thu, 5 Mar 2020 15:49:56 +0300 Subject: [PATCH 016/170] StdGen = SMGen (#22) * StdGen = SMGen * Remove dependency on "time" --- System/Random.hs | 196 ++++------------------------------------------- cabal.project | 2 + random.cabal | 3 +- 3 files changed, 19 insertions(+), 182 deletions(-) create mode 100644 cabal.project diff --git a/System/Random.hs b/System/Random.hs index db78fd69c..9016702fd 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -40,9 +40,7 @@ -- instance of 'Random' allows one to generate random values of type -- 'Float'. -- --- This implementation uses the Portable Combined Generator of L'Ecuyer --- ["System.Random\#LEcuyer"] for 32-bit computers, transliterated by --- Lennart Augustsson. It has a period of roughly 2.30584e18. +-- This implementation uses the SplitMix algorithm [1]. -- ----------------------------------------------------------------------------- @@ -111,16 +109,11 @@ import Foreign.Ptr (plusPtr) import Foreign.Storable (peekByteOff, pokeByteOff) import GHC.ForeignPtr -import Data.Char (chr, isSpace, ord) import Data.IORef (IORef, newIORef, readIORef, writeIORef, atomicModifyIORef') -import Data.Ratio (denominator, numerator) -import Data.Time (UTCTime(..), getCurrentTime) -import System.CPUTime (getCPUTime) import System.IO.Unsafe (unsafePerformIO) +import qualified System.Random.SplitMix as SM import GHC.Exts (Ptr(..), build, byteArrayContents#, unsafeCoerce#) -import Numeric (readDec) - mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 {-# INLINE mutableByteArrayContentsCompat #-} @@ -131,12 +124,6 @@ mutableByteArrayContentsCompat (MutableByteArray arr#) mutableByteArrayContentsCompat = mutableByteArrayContents #endif -getTime :: IO (Integer, Integer) -getTime = do - utc <- getCurrentTime - let daytime = toRational $ utctDayTime utc - return $ quotRem (numerator daytime) (denominator daytime) - -- | The class 'RandomGen' provides a common interface to random number -- generators. -- @@ -193,11 +180,7 @@ class RandomGen g where genRange _ = (minBound, maxBound) -- |The 'split' operation allows one to obtain two distinct random number - -- generators. This is very useful in functional programs (for example, when - -- passing a random number generator down to recursive calls), but very - -- little work has been done on statistically robust implementations of - -- 'split' (["System.Random\#Burton", "System.Random\#Hellekalek"] - -- are the only examples we know of). + -- generators. split :: g -> (g, g) @@ -343,66 +326,13 @@ runStateTGen_ :: (RandomGen g, Functor f) => g -> StateT g f a -> f a runStateTGen_ g = fmap fst . flip runStateT g -{- | -The 'StdGen' instance of 'RandomGen' has a 'genRange' of at least 30 bits. - -The result of repeatedly using 'next' should be at least as statistically -robust as the /Minimal Standard Random Number Generator/ described by -["System.Random\#Park", "System.Random\#Carta"]. -Until more is known about implementations of 'split', all we require is -that 'split' deliver generators that are (a) not identical and -(b) independently robust in the sense just given. - -The 'Show' and 'Read' instances of 'StdGen' provide a primitive way to save the -state of a random number generator. -It is required that @'read' ('show' g) == g@. - -In addition, 'reads' may be used to map an arbitrary string (not necessarily one -produced by 'show') onto a value of type 'StdGen'. In general, the 'Read' -instance of 'StdGen' has the following properties: - -* It guarantees to succeed on any string. - -* It guarantees to consume only a finite portion of the string. - -* Different argument strings are likely to result in different results. - --} - -data StdGen - = StdGen !Int32 !Int32 +type StdGen = SM.SMGen instance RandomGen StdGen where - next = stdNext - genRange _ = stdRange - - split = stdSplit - -instance Show StdGen where - showsPrec p (StdGen s1 s2) = - showsPrec p s1 . - showChar ' ' . - showsPrec p s2 - -instance Read StdGen where - readsPrec _p = \ r -> - case try_read r of - r'@[_] -> r' - _ -> [stdFromString r] -- because it shouldn't ever fail. - where - try_read r = do - (s1, r1) <- readDec (dropWhile isSpace r) - (s2, r2) <- readDec (dropWhile isSpace r1) - return (StdGen s1 s2, r2) - -{- - If we cannot unravel the StdGen from a string, create - one based on the string given. --} -stdFromString :: String -> (StdGen, String) -stdFromString s = (mkStdGen num, rest) - where (cs, rest) = splitAt 6 s - num = foldl (\a x -> x + 3 * a) 1 (map ord cs) + next = SM.nextInt + genWord32 = SM.nextWord32 + genWord64 = SM.nextWord64 + split = SM.splitSMGen {- | @@ -411,24 +341,7 @@ generator, by mapping an 'Int' into a generator. Again, distinct arguments should be likely to produce distinct generators. -} mkStdGen :: Int -> StdGen -- why not Integer ? -mkStdGen s = mkStdGen32 $ fromIntegral s - -{- -From ["System.Random\#LEcuyer"]: "The integer variables s1 and s2 ... must be -initialized to values in the range [1, 2147483562] and [1, 2147483398] -respectively." --} -mkStdGen32 :: Int32 -> StdGen -mkStdGen32 sMaybeNegative = StdGen (s1+1) (s2+1) - where - -- We want a non-negative number, but we can't just take the abs - -- of sMaybeNegative as -minBound == minBound. - s = sMaybeNegative .&. maxBound - (q, s1) = s `divMod` 2147483562 - s2 = q `mod` 2147483398 - -createStdGen :: Integer -> StdGen -createStdGen s = mkStdGen32 $ fromIntegral s +mkStdGen s = SM.mkSMGen $ fromIntegral s {- | With a source of random number supply in hand, the 'Random' class allows the @@ -792,15 +705,6 @@ randomFloat rng = -- -- random rng = case random rng of -- -- (x,rng') -> (realToFrac (x::Double), rng') -mkStdRNG :: Integer -> IO StdGen -mkStdRNG o = do - ct <- getCPUTime - (sec, psec) <- getTime - return (createStdGen (sec * 12345 + psec + ct + o)) - -randomBounded :: (RandomGen g, Random a, Bounded a) => g -> (a, g) -randomBounded = randomR (minBound, maxBound) - -- The two integer functions below take an [inclusive,inclusive] range. randomIvalIntegral :: (RandomGen g, Integral a) => (a, a) -> g -> (a, g) randomIvalIntegral (l,h) = randomIvalInteger (toInteger l, toInteger h) @@ -833,25 +737,6 @@ randomIvalInteger (l,h) rng v' = (v * b + (fromIntegral x - fromIntegral genlo)) --- The continuous functions on the other hand take an [inclusive,exclusive) range. -randomFrac :: (RandomGen g, Fractional a) => g -> (a, g) -randomFrac = randomIvalDouble (0::Double,1) realToFrac - -randomIvalDouble :: (RandomGen g, Fractional a) => (Double, Double) -> (Double -> a) -> g -> (a, g) -randomIvalDouble (l,h) fromDouble rng - | l > h = randomIvalDouble (h,l) fromDouble rng - | otherwise = - case (randomIvalInteger (toInteger (minBound::Int32), toInteger (maxBound::Int32)) rng) of - (x, rng') -> - let - scaled_x = - fromDouble (0.5*l + 0.5*h) + -- previously (l+h)/2, overflowed - fromDouble ((0.5*h - 0.5*l) / (0.5 * realToFrac int32Count)) * -- avoid overflow - fromIntegral (x::Int32) - in - (scaled_x, rng') - - bitmaskWithRejection :: (RandomGen g, FiniteBits a, Num a, Ord a, Random a) => (a, a) @@ -912,42 +797,6 @@ bitmaskWithRejection32M = bitmaskWithRejectionM uniformWord32 bitmaskWithRejection64M :: MonadRandom g m => Word64 -> g -> m Word64 bitmaskWithRejection64M = bitmaskWithRejectionM uniformWord64 -int32Count :: Integer -int32Count = toInteger (maxBound::Int32) - toInteger (minBound::Int32) + 1 -- GHC ticket #3982 - -stdRange :: (Int,Int) -stdRange = (1, 2147483562) - -stdNext :: StdGen -> (Int, StdGen) --- Returns values in the range stdRange -stdNext (StdGen s1 s2) = (fromIntegral z', StdGen s1'' s2'') - where z' = if z < 1 then z + 2147483562 else z - z = s1'' - s2'' - - k = s1 `quot` 53668 - s1' = 40014 * (s1 - k * 53668) - k * 12211 - s1'' = if s1' < 0 then s1' + 2147483563 else s1' - - k' = s2 `quot` 52774 - s2' = 40692 * (s2 - k' * 52774) - k' * 3791 - s2'' = if s2' < 0 then s2' + 2147483399 else s2' - -stdSplit :: StdGen -> (StdGen, StdGen) -stdSplit std@(StdGen s1 s2) - = (left, right) - where - -- no statistical foundation for this! - left = StdGen new_s1 t2 - right = StdGen t1 new_s2 - - new_s1 | s1 == 2147483562 = 1 - | otherwise = s1 + 1 - - new_s2 | s2 == 1 = 2147483398 - | otherwise = s2 - 1 - - StdGen t1 t2 = snd (next std) - -- The global random number generator {- $globalrng #globalrng# @@ -968,9 +817,8 @@ getStdGen :: IO StdGen getStdGen = readIORef theStdGen theStdGen :: IORef StdGen -theStdGen = unsafePerformIO $ do - rng <- mkStdRNG 0 - newIORef rng +theStdGen = unsafePerformIO $ SM.initSMGen >>= newIORef +{-# NOINLINE theStdGen #-} -- |Applies 'split' to the current global random generator, -- updates it with one of the results, and returns the other. @@ -993,22 +841,10 @@ getStdRandom f = atomicModifyIORef' theStdGen (swap . f) {- $references -1. FW #Burton# Burton and RL Page, /Distributed random number generation/, -Journal of Functional Programming, 2(2):203-212, April 1992. - -2. SK #Park# Park, and KW Miller, /Random number generators - -good ones are hard to find/, Comm ACM 31(10), Oct 1988, pp1192-1201. - -3. DG #Carta# Carta, /Two fast implementations of the minimal standard -random number generator/, Comm ACM, 33(1), Jan 1990, pp87-88. - -4. P #Hellekalek# Hellekalek, /Don\'t trust parallel Monte Carlo/, -Department of Mathematics, University of Salzburg, -, 1998. - -5. Pierre #LEcuyer# L'Ecuyer, /Efficient and portable combined random -number generators/, Comm ACM, 31(6), Jun 1988, pp742-749. - -The Web site is a great source of information. +1. Guy L. Steele, Jr., Doug Lea, and Christine H. Flood. 2014. Fast splittable +pseudorandom number generators. In Proceedings of the 2014 ACM International +Conference on Object Oriented Programming Systems Languages & Applications +(OOPSLA '14). ACM, New York, NY, USA, 453-472. DOI: +https://doi.org/10.1145/2660193.2660195 -} diff --git a/cabal.project b/cabal.project new file mode 100644 index 000000000..3f330dd76 --- /dev/null +++ b/cabal.project @@ -0,0 +1,2 @@ +packages: . +constraints: splitmix -random diff --git a/random.cabal b/random.cabal index e51281427..638ddfdda 100644 --- a/random.cabal +++ b/random.cabal @@ -33,9 +33,8 @@ Library build-depends: base >= 3 && < 5 , bytestring , primitive - , time , mtl - , mwc-random + , splitmix source-repository head type: git From cd943225147f71f9b21372074b1939b954ddb62d Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 6 Mar 2020 16:38:33 +0100 Subject: [PATCH 017/170] Export Uniform and UniformRange --- System/Random.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/System/Random.hs b/System/Random.hs index 9016702fd..f64c45d94 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -76,6 +76,8 @@ module System.Random , newStdGen -- * Random values of various types + , Uniform(..) + , UniformRange(..) , Random(..) -- * Generators for sequences of bytes From 9564db4bcdd6710e900610a14b69ef9faed22be2 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 1 Mar 2020 22:17:56 +0300 Subject: [PATCH 018/170] Implement PrimGen --- System/Random.hs | 108 +++++++++++++++++++++++++++++++++-------------- stack.yaml | 68 +++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 31 deletions(-) create mode 100644 stack.yaml diff --git a/System/Random.hs b/System/Random.hs index f64c45d94..c058092a9 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -58,13 +58,20 @@ module System.Random , mkStdGen -- * Stateful interface for pure generators + -- ** Based on StateT + , PureGen , splitGen , genRandom - , runStateGen - , runStateGen_ - , runStateTGen - , runStateTGen_ + , runGenState + , runGenState_ + , runGenStateT + , runGenStateT_ , runPureGenST + -- ** Based on PrimMonad + , PrimGen + , runPrimGenST + , runPrimGenIO + , atomicPrimGen -- ** The global random number generator @@ -90,38 +97,38 @@ module System.Random ) where -import Prelude - import Control.Arrow +import Control.Monad.IO.Class import Control.Monad.Primitive import Control.Monad.ST import Control.Monad.State.Strict import Data.Bits -import Data.Int -import Data.Primitive.ByteArray -import Data.Word -import Foreign.C.Types - import Data.ByteString.Builder.Prim (word64LE) import Data.ByteString.Builder.Prim.Internal (runF) import Data.ByteString.Internal (ByteString(PS)) import Data.ByteString.Short.Internal (ShortByteString(SBS), fromShort) +import Data.Char (isSpace, ord) +import Data.Int +import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef, writeIORef) +import Data.Primitive.ByteArray +import Data.Primitive.MutVar +import Data.Ratio (denominator, numerator) +import Data.Word +import Foreign.C.Types import Foreign.Marshal.Alloc (alloca) import Foreign.Ptr (plusPtr) import Foreign.Storable (peekByteOff, pokeByteOff) +import GHC.Exts (Ptr(..), build) import GHC.ForeignPtr - -import Data.IORef (IORef, newIORef, readIORef, writeIORef, atomicModifyIORef') import System.IO.Unsafe (unsafePerformIO) import qualified System.Random.SplitMix as SM -import GHC.Exts (Ptr(..), build, byteArrayContents#, unsafeCoerce#) - mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 {-# INLINE mutableByteArrayContentsCompat #-} #if !MIN_VERSION_primitive(0,7,0) -mutableByteArrayContentsCompat (MutableByteArray arr#) - = Ptr (byteArrayContents# (unsafeCoerce# arr#)) +mutableByteArrayContentsCompat mba = + case mutableByteArrayContents mba of + Addr addr# -> Ptr addr# #else mutableByteArrayContentsCompat = mutableByteArrayContents #endif @@ -284,7 +291,7 @@ genByteString n g = runPureGenST g (uniformByteStringPrim n) -- -- @since 1.2 runPureGenST :: RandomGen g => g -> (forall s . PureGen g -> StateT g (ST s) a) -> (a, g) -runPureGenST g action = runST $ runStateTGen g $ action PureGen +runPureGenST g action = runST $ runGenStateT g $ action PureGen {-# INLINE runPureGenST #-} @@ -315,18 +322,57 @@ genRandom = randomM PureGen splitGen :: (MonadState g m, RandomGen g) => m g splitGen = state split -runStateGen :: RandomGen g => g -> State g a -> (a, g) -runStateGen = flip runState +runGenState :: RandomGen g => g -> State g a -> (a, g) +runGenState = flip runState -runStateGen_ :: RandomGen g => g -> State g a -> a -runStateGen_ g = fst . flip runState g +runGenState_ :: RandomGen g => g -> State g a -> a +runGenState_ g = fst . flip runState g -runStateTGen :: RandomGen g => g -> StateT g m a -> m (a, g) -runStateTGen = flip runStateT +runGenStateT :: RandomGen g => g -> StateT g m a -> m (a, g) +runGenStateT = flip runStateT -runStateTGen_ :: (RandomGen g, Functor f) => g -> StateT g f a -> f a -runStateTGen_ g = fmap fst . flip runStateT g +runGenStateT_ :: (RandomGen g, Functor f) => g -> StateT g f a -> f a +runGenStateT_ g = fmap fst . flip runStateT g +-- | This is a wrapper wround pure generator that can be used in an effectful environment. +-- It is safe in presence of concurrency since all operations are performed atomically. +-- +-- @since 1.2 +newtype PrimGen s g = PrimGen (MutVar s g) + +instance (s ~ PrimState m, PrimMonad m, RandomGen g) => + MonadRandom (PrimGen s g) m where + type Seed (PrimGen s g) = g + restore = fmap PrimGen . newMutVar + save (PrimGen gVar) = readMutVar gVar + uniformWord32R r = atomicPrimGen (genWord32R r) + uniformWord64R r = atomicPrimGen (genWord64R r) + uniformWord8 = atomicPrimGen genWord8 + uniformWord16 = atomicPrimGen genWord16 + uniformWord32 = atomicPrimGen genWord32 + uniformWord64 = atomicPrimGen genWord64 + uniformByteArray n = atomicPrimGen (genByteArray n) + +-- | Apply a pure operation to generator atomically. +atomicPrimGen :: PrimMonad m => (g -> (a, g)) -> PrimGen (PrimState m) g -> m a +atomicPrimGen op (PrimGen gVar) = + atomicModifyMutVar' gVar $ \g -> + case op g of + (a, g') -> (g', a) + +runPrimGenST :: RandomGen g => g -> (forall s . PrimGen s g -> ST s a) -> (a, g) +runPrimGenST g action = runST $ do + primGen :: PrimGen s g <- restore g + res <- action primGen + g' <- save primGen + pure (res, g') + +runPrimGenIO :: (RandomGen g, MonadIO m) => g -> (PrimGen RealWorld g -> m a) -> m (a, g) +runPrimGenIO g action = do + primGen :: PrimGen s g <- liftIO $ restore g + res <- action primGen + g' <- liftIO $ save primGen + pure (res, g') type StdGen = SM.SMGen @@ -375,7 +421,7 @@ class Random a where {-# INLINE randomR #-} randomR :: RandomGen g => (a, a) -> g -> (a, g) default randomR :: (RandomGen g, UniformRange a) => (a, a) -> g -> (a, g) - randomR r g = runStateGen g (uniformR r PureGen) + randomR r g = runGenState g (uniformR r PureGen) -- | The same as 'randomR', but using a default range determined by the type: -- @@ -388,7 +434,7 @@ class Random a where -- * For 'Integer', the range is (arbitrarily) the range of 'Int'. {-# INLINE random #-} random :: RandomGen g => g -> (a, g) - random g = runStateGen g genRandom + random g = runGenState g genRandom {-# INLINE randomM #-} randomM :: MonadRandom g m => g -> m a @@ -639,7 +685,7 @@ instance UniformRange Bool where where bool2Int :: Bool -> Int bool2Int False = 0 - bool2Int True = 1 + bool2Int True = 1 int2Bool :: Int -> Bool int2Bool 0 = False int2Bool _ = True @@ -782,11 +828,11 @@ bitmaskWithRejectionRM (bottom, top) gen {-# INLINE bitmaskWithRejectionRM #-} bitmaskWithRejectionM :: (Ord a, FiniteBits a, Num a, MonadRandom g m) => (g -> m a) -> a -> g -> m a -bitmaskWithRejectionM uniform range gen = go +bitmaskWithRejectionM genUniform range gen = go where mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) go = do - x <- uniform gen + x <- genUniform gen let x' = x .&. mask if x' >= range then go diff --git a/stack.yaml b/stack.yaml new file mode 100644 index 000000000..9acf81245 --- /dev/null +++ b/stack.yaml @@ -0,0 +1,68 @@ +# This file was automatically generated by 'stack init' +# +# Some commonly used options have been documented as comments in this file. +# For advanced use and comprehensive documentation of the format, please see: +# https://docs.haskellstack.org/en/stable/yaml_configuration/ + +# Resolver to choose a 'specific' stackage snapshot or a compiler version. +# A snapshot resolver dictates the compiler version and the set of packages +# to be used for project dependencies. For example: +# +# resolver: lts-3.5 +# resolver: nightly-2015-09-21 +# resolver: ghc-7.10.2 +# +# The location of a snapshot can be provided as a file or url. Stack assumes +# a snapshot provided as a file might change, whereas a url resource does not. +# +# resolver: ./custom-snapshot.yaml +# resolver: https://example.com/snapshots/2018-01-01.yaml +resolver: lts-15.1 + +# User packages to be built. +# Various formats can be used as shown in the example below. +# +# packages: +# - some-directory +# - https://example.com/foo/bar/baz-0.0.2.tar.gz +# subdirs: +# - auto-update +# - wai +packages: +- . +# Dependency packages to be pulled from upstream that are not in the resolver. +# These entries can reference officially published versions as well as +# forks / in-progress versions pinned to a git hash. For example: +# +# extra-deps: +# - acme-missiles-0.3 +# - git: https://github.com/commercialhaskell/stack.git +# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a +# +# extra-deps: [] + +# Override default flag values for local packages and extra-deps +flags: + splitmix: + random: false + +# Extra package databases containing global packages +# extra-package-dbs: [] + +# Control whether we use the GHC we find on the path +# system-ghc: true +# +# Require a specific version of stack, using version ranges +# require-stack-version: -any # Default +# require-stack-version: ">=2.2" +# +# Override the architecture used by stack, especially useful on Windows +# arch: i386 +# arch: x86_64 +# +# Extra directories used by stack for building +# extra-include-dirs: [/path/to/dir] +# extra-lib-dirs: [/path/to/dir] +# +# Allow a newer minor version of GHC than the snapshot specifies +# compiler-check: newer-minor From b918908492f929674b371c33f9df34745dc04611 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 1 Mar 2020 22:29:55 +0300 Subject: [PATCH 019/170] Add ability to split PrimGen --- System/Random.hs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/System/Random.hs b/System/Random.hs index c058092a9..1579ba4fb 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -71,6 +71,7 @@ module System.Random , PrimGen , runPrimGenST , runPrimGenIO + , splitPrimGen , atomicPrimGen -- ** The global random number generator @@ -360,6 +361,17 @@ atomicPrimGen op (PrimGen gVar) = case op g of (a, g') -> (g', a) + +-- | Split `PrimGen` into atomically updated current generator and a newly created that is +-- returned. +-- +-- @since 1.2 +splitPrimGen :: + (RandomGen g, PrimMonad m) + => PrimGen (PrimState m) g + -> m (PrimGen (PrimState m) g) +splitPrimGen = atomicPrimGen split >=> restore + runPrimGenST :: RandomGen g => g -> (forall s . PrimGen s g -> ST s a) -> (a, g) runPrimGenST g action = runST $ do primGen :: PrimGen s g <- restore g From 58495129a668509062e45999c77c0e7b03dab1f9 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Tue, 3 Mar 2020 01:25:30 +0300 Subject: [PATCH 020/170] Implement MutGen. Remove default implmenetation for `randomM` to avoid breakage --- System/Random.hs | 189 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 151 insertions(+), 38 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 1579ba4fb..336166ff0 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -68,11 +68,22 @@ module System.Random , runGenStateT_ , runPureGenST -- ** Based on PrimMonad + -- *** PrimGen - boxed thread safe state , PrimGen , runPrimGenST + , runPrimGenST_ , runPrimGenIO + , runPrimGenIO_ , splitPrimGen , atomicPrimGen + -- *** MutGen - unboxed mutable state + , MutGen + , runMutGenST + , runMutGenST_ + , runMutGenIO + , runMutGenIO_ + , splitMutGen + , applyMutGen -- ** The global random number generator @@ -113,6 +124,7 @@ import Data.Int import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef, writeIORef) import Data.Primitive.ByteArray import Data.Primitive.MutVar +import Data.Primitive.Types as Primitive (Prim, sizeOf) import Data.Ratio (denominator, numerator) import Data.Word import Foreign.C.Types @@ -124,15 +136,17 @@ import GHC.ForeignPtr import System.IO.Unsafe (unsafePerformIO) import qualified System.Random.SplitMix as SM -mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 -{-# INLINE mutableByteArrayContentsCompat #-} #if !MIN_VERSION_primitive(0,7,0) +import Data.Primitive.Types (Addr(..)) + mutableByteArrayContentsCompat mba = case mutableByteArrayContents mba of Addr addr# -> Ptr addr# #else mutableByteArrayContentsCompat = mutableByteArrayContents #endif +mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 +{-# INLINE mutableByteArrayContentsCompat #-} -- | The class 'RandomGen' provides a common interface to random number -- generators. @@ -352,6 +366,7 @@ instance (s ~ PrimState m, PrimMonad m, RandomGen g) => uniformWord16 = atomicPrimGen genWord16 uniformWord32 = atomicPrimGen genWord32 uniformWord64 = atomicPrimGen genWord64 + {-# INLINE uniformWord64 #-} uniformByteArray n = atomicPrimGen (genByteArray n) -- | Apply a pure operation to generator atomically. @@ -360,6 +375,7 @@ atomicPrimGen op (PrimGen gVar) = atomicModifyMutVar' gVar $ \g -> case op g of (a, g') -> (g', a) +{-# INLINE atomicPrimGen #-} -- | Split `PrimGen` into atomically updated current generator and a newly created that is @@ -374,17 +390,84 @@ splitPrimGen = atomicPrimGen split >=> restore runPrimGenST :: RandomGen g => g -> (forall s . PrimGen s g -> ST s a) -> (a, g) runPrimGenST g action = runST $ do - primGen :: PrimGen s g <- restore g + primGen <- restore g res <- action primGen g' <- save primGen pure (res, g') +-- | Same as `runPrimGenST`, but discard the resulting generator. +runPrimGenST_ :: RandomGen g => g -> (forall s . PrimGen s g -> ST s a) -> a +runPrimGenST_ g action = fst $ runPrimGenST g action + runPrimGenIO :: (RandomGen g, MonadIO m) => g -> (PrimGen RealWorld g -> m a) -> m (a, g) runPrimGenIO g action = do - primGen :: PrimGen s g <- liftIO $ restore g + primGen <- liftIO $ restore g res <- action primGen g' <- liftIO $ save primGen pure (res, g') +{-# INLINE runPrimGenIO #-} + +-- | Same as `runPrimGenIO`, but discard the resulting generator. +runPrimGenIO_ :: (RandomGen g, MonadIO m) => g -> (PrimGen RealWorld g -> m a) -> m a +runPrimGenIO_ g action = fst <$> runPrimGenIO g action +{-# INLINE runPrimGenIO_ #-} + + +newtype MutGen s g = MutGen (MutableByteArray s) + +instance (s ~ PrimState m, PrimMonad m, RandomGen g, Prim g) => + MonadRandom (MutGen s g) m where + type Seed (MutGen s g) = g + restore g = do + ma <- newByteArray (Primitive.sizeOf g) + writeByteArray ma 0 g + pure $ MutGen ma + save (MutGen ma) = readByteArray ma 0 + uniformWord32R r = applyMutGen (genWord32R r) + uniformWord64R r = applyMutGen (genWord64R r) + uniformWord8 = applyMutGen genWord8 + uniformWord16 = applyMutGen genWord16 + uniformWord32 = applyMutGen genWord32 + uniformWord64 = applyMutGen genWord64 + uniformByteArray n = applyMutGen (genByteArray n) + +applyMutGen :: (Prim g, PrimMonad m) => (g -> (a, g)) -> MutGen (PrimState m) g -> m a +applyMutGen f (MutGen ma) = do + g <- readByteArray ma 0 + case f g of + (res, g') -> res <$ writeByteArray ma 0 g' + +-- | Split `MutGen` into atomically updated current generator and a newly created that is +-- returned. +-- +-- @since 1.2 +splitMutGen :: + (Prim g, RandomGen g, PrimMonad m) + => MutGen (PrimState m) g + -> m (MutGen (PrimState m) g) +splitMutGen = applyMutGen split >=> restore + +runMutGenST :: (Prim g, RandomGen g) => g -> (forall s . MutGen s g -> ST s a) -> (a, g) +runMutGenST g action = runST $ do + mutGen <- restore g + res <- action mutGen + g' <- save mutGen + pure (res, g') + +-- | Same as `runMutGenST`, but discard the resulting generator. +runMutGenST_ :: (Prim g, RandomGen g) => g -> (forall s . MutGen s g -> ST s a) -> a +runMutGenST_ g action = fst $ runMutGenST g action + +runMutGenIO :: (Prim g, RandomGen g, MonadIO m) => g -> (MutGen RealWorld g -> m a) -> m (a, g) +runMutGenIO g action = do + mutGen <- liftIO $ restore g + res <- action mutGen + g' <- liftIO $ save mutGen + pure (res, g') + +-- | Same as `runMutGenIO`, but discard the resulting generator. +runMutGenIO_ :: (Prim g, RandomGen g, MonadIO m) => g -> (MutGen RealWorld g -> m a) -> m a +runMutGenIO_ g action = fst <$> runMutGenIO g action type StdGen = SM.SMGen @@ -448,10 +531,10 @@ class Random a where random :: RandomGen g => g -> (a, g) random g = runGenState g genRandom - {-# INLINE randomM #-} + --{-# INLINE randomM #-} randomM :: MonadRandom g m => g -> m a - default randomM :: (MonadRandom g m, Uniform a) => g -> m a - randomM = uniform + -- default randomM :: (MonadRandom g m, Uniform a) => g -> m a + -- randomM = uniform -- | Plural variant of 'randomR', producing an infinite list of -- random values instead of returning a new generator. @@ -495,28 +578,32 @@ instance Random Integer where instance UniformRange Integer where --uniformR ival g = randomIvalInteger ival g -- FIXME -instance Random Int8 +instance Random Int8 where + randomM = uniform instance Uniform Int8 where uniform = fmap (fromIntegral :: Word8 -> Int8) . uniformWord8 instance UniformRange Int8 where -instance Random Int16 +instance Random Int16 where + randomM = uniform instance Uniform Int16 where uniform = fmap (fromIntegral :: Word16 -> Int16) . uniformWord16 instance UniformRange Int16 where -instance Random Int32 +instance Random Int32 where + randomM = uniform instance Uniform Int32 where uniform = fmap (fromIntegral :: Word32 -> Int32) . uniformWord32 instance UniformRange Int32 where -instance Random Int64 +instance Random Int64 where + randomM = uniform instance Uniform Int64 where uniform = fmap (fromIntegral :: Word64 -> Int64) . uniformWord64 instance UniformRange Int64 where -instance Random Int where - randomR = bitmaskWithRejection +instance Random Int where + randomM = uniform instance Uniform Int where #if WORD_SIZE_IN_BITS < 64 uniform = fmap (fromIntegral :: Word32 -> Int) . uniformWord32 @@ -525,7 +612,8 @@ instance Uniform Int where #endif instance UniformRange Int where -instance Random Word +instance Random Word where + randomM = uniform instance Uniform Word where #if WORD_SIZE_IN_BITS < 64 uniform = fmap (fromIntegral :: Word32 -> Word) . uniformWord32 @@ -536,7 +624,8 @@ instance UniformRange Word where {-# INLINE uniformR #-} uniformR = bitmaskWithRejectionRM -instance Random Word8 +instance Random Word8 where + randomM = uniform instance Uniform Word8 where {-# INLINE uniform #-} uniform = uniformWord8 @@ -544,7 +633,8 @@ instance UniformRange Word8 where {-# INLINE uniformR #-} uniformR = bitmaskWithRejectionRM -instance Random Word16 +instance Random Word16 where + randomM = uniform instance Uniform Word16 where {-# INLINE uniform #-} uniform = uniformWord16 @@ -552,7 +642,8 @@ instance UniformRange Word16 where {-# INLINE uniformR #-} uniformR = bitmaskWithRejectionRM -instance Random Word32 +instance Random Word32 where + randomM = uniform instance Uniform Word32 where {-# INLINE uniform #-} uniform = uniformWord32 @@ -560,7 +651,8 @@ instance UniformRange Word32 where {-# INLINE uniformR #-} uniformR = bitmaskWithRejectionRM -instance Random Word64 +instance Random Word64 where + randomM = uniform instance Uniform Word64 where {-# INLINE uniform #-} uniform = uniformWord64 @@ -569,127 +661,148 @@ instance UniformRange Word64 where uniformR = bitmaskWithRejectionRM -instance Random CChar +instance Random CChar where + randomM = uniform instance Uniform CChar where uniform = fmap CChar . uniform instance UniformRange CChar where uniformR (CChar b, CChar t) = fmap CChar . uniformR (b, t) -instance Random CSChar +instance Random CSChar where + randomM = uniform instance Uniform CSChar where uniform = fmap CSChar . uniform instance UniformRange CSChar where uniformR (CSChar b, CSChar t) = fmap CSChar . uniformR (b, t) -instance Random CUChar +instance Random CUChar where + randomM = uniform instance Uniform CUChar where uniform = fmap CUChar . uniform instance UniformRange CUChar where uniformR (CUChar b, CUChar t) = fmap CUChar . uniformR (b, t) -instance Random CShort +instance Random CShort where + randomM = uniform instance Uniform CShort where uniform = fmap CShort . uniform instance UniformRange CShort where uniformR (CShort b, CShort t) = fmap CShort . uniformR (b, t) -instance Random CUShort +instance Random CUShort where + randomM = uniform instance Uniform CUShort where uniform = fmap CUShort . uniform instance UniformRange CUShort where uniformR (CUShort b, CUShort t) = fmap CUShort . uniformR (b, t) -instance Random CInt +instance Random CInt where + randomM = uniform instance Uniform CInt where uniform = fmap CInt . uniform instance UniformRange CInt where uniformR (CInt b, CInt t) = fmap CInt . uniformR (b, t) -instance Random CUInt +instance Random CUInt where + randomM = uniform instance Uniform CUInt where uniform = fmap CUInt . uniform instance UniformRange CUInt where uniformR (CUInt b, CUInt t) = fmap CUInt . uniformR (b, t) -instance Random CLong +instance Random CLong where + randomM = uniform instance Uniform CLong where uniform = fmap CLong . uniform instance UniformRange CLong where uniformR (CLong b, CLong t) = fmap CLong . uniformR (b, t) -instance Random CULong +instance Random CULong where + randomM = uniform instance Uniform CULong where uniform = fmap CULong . uniform instance UniformRange CULong where uniformR (CULong b, CULong t) = fmap CULong . uniformR (b, t) -instance Random CPtrdiff +instance Random CPtrdiff where + randomM = uniform instance Uniform CPtrdiff where uniform = fmap CPtrdiff . uniform instance UniformRange CPtrdiff where uniformR (CPtrdiff b, CPtrdiff t) = fmap CPtrdiff . uniformR (b, t) -instance Random CSize +instance Random CSize where + randomM = uniform instance Uniform CSize where uniform = fmap CSize . uniform instance UniformRange CSize where uniformR (CSize b, CSize t) = fmap CSize . uniformR (b, t) -instance Random CWchar +instance Random CWchar where + randomM = uniform instance Uniform CWchar where uniform = fmap CWchar . uniform instance UniformRange CWchar where uniformR (CWchar b, CWchar t) = fmap CWchar . uniformR (b, t) -instance Random CSigAtomic +instance Random CSigAtomic where + randomM = uniform instance Uniform CSigAtomic where uniform = fmap CSigAtomic . uniform instance UniformRange CSigAtomic where uniformR (CSigAtomic b, CSigAtomic t) = fmap CSigAtomic . uniformR (b, t) -instance Random CLLong +instance Random CLLong where + randomM = uniform instance Uniform CLLong where uniform = fmap CLLong . uniform instance UniformRange CLLong where uniformR (CLLong b, CLLong t) = fmap CLLong . uniformR (b, t) -instance Random CULLong +instance Random CULLong where + randomM = uniform instance Uniform CULLong where uniform = fmap CULLong . uniform instance UniformRange CULLong where uniformR (CULLong b, CULLong t) = fmap CULLong . uniformR (b, t) -instance Random CIntPtr +instance Random CIntPtr where + randomM = uniform instance Uniform CIntPtr where uniform = fmap CIntPtr . uniform instance UniformRange CIntPtr where uniformR (CIntPtr b, CIntPtr t) = fmap CIntPtr . uniformR (b, t) -instance Random CUIntPtr +instance Random CUIntPtr where + randomM = uniform instance Uniform CUIntPtr where uniform = fmap CUIntPtr . uniform instance UniformRange CUIntPtr where uniformR (CUIntPtr b, CUIntPtr t) = fmap CUIntPtr . uniformR (b, t) -instance Random CIntMax +instance Random CIntMax where + randomM = uniform instance Uniform CIntMax where uniform = fmap CIntMax . uniform instance UniformRange CIntMax where uniformR (CIntMax b, CIntMax t) = fmap CIntMax . uniformR (b, t) -instance Random CUIntMax +instance Random CUIntMax where + randomM = uniform instance Uniform CUIntMax where uniform = fmap CUIntMax . uniform instance UniformRange CUIntMax where uniformR (CUIntMax b, CUIntMax t) = fmap CUIntMax . uniformR (b, t) -instance Random Char +instance Random Char where + randomM = uniform instance Uniform Char where uniform = uniformR (minBound, maxBound) instance UniformRange Char where -- FIXME instance Random Bool where + randomM = uniform instance Uniform Bool where uniform = uniformR (minBound, maxBound) instance UniformRange Bool where From 2395472d8edc2a26e1118f6c2e313d200ba448c0 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 13 Mar 2020 16:31:32 +0100 Subject: [PATCH 021/170] Remove unnecessary imports --- System/Random.hs | 2 -- 1 file changed, 2 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 336166ff0..8c40e8af0 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -119,13 +119,11 @@ import Data.ByteString.Builder.Prim (word64LE) import Data.ByteString.Builder.Prim.Internal (runF) import Data.ByteString.Internal (ByteString(PS)) import Data.ByteString.Short.Internal (ShortByteString(SBS), fromShort) -import Data.Char (isSpace, ord) import Data.Int import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef, writeIORef) import Data.Primitive.ByteArray import Data.Primitive.MutVar import Data.Primitive.Types as Primitive (Prim, sizeOf) -import Data.Ratio (denominator, numerator) import Data.Word import Foreign.C.Types import Foreign.Marshal.Alloc (alloca) From 834aa5479956b7f63741ae6ff020e5505d30865d Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Wed, 11 Mar 2020 18:10:41 +0000 Subject: [PATCH 022/170] doctests to explain the new features in random and forward direction --- Setup.hs | 27 +++++++++++++++ System/Random.hs | 85 +++++++++++++++++++++++++++++++++++++++++++++++ random.cabal | 29 ++++++++++------ tests/doctests.hs | 12 +++++++ 4 files changed, 142 insertions(+), 11 deletions(-) create mode 100644 tests/doctests.hs diff --git a/Setup.hs b/Setup.hs index 6fa548caf..8ec54a08d 100644 --- a/Setup.hs +++ b/Setup.hs @@ -1,6 +1,33 @@ +{-# LANGUAGE CPP #-} +{-# OPTIONS_GHC -Wall #-} module Main (main) where +#ifndef MIN_VERSION_cabal_doctest +#define MIN_VERSION_cabal_doctest(x,y,z) 0 +#endif + +#if MIN_VERSION_cabal_doctest(1,0,0) + +import Distribution.Extra.Doctest ( defaultMainWithDoctests ) +main :: IO () +main = defaultMainWithDoctests "doctests" + +#else + +#ifdef MIN_VERSION_Cabal +-- If the macro is defined, we have new cabal-install, +-- but for some reason we don't have cabal-doctest in package-db +-- +-- Probably we are running cabal sdist, when otherwise using new-build +-- workflow +#warning You are configuring this package without cabal-doctest installed. \ + The doctests test-suite will not work as a result. \ + To fix this, install cabal-doctest before configuring. +#endif + import Distribution.Simple main :: IO () main = defaultMain + +#endif diff --git a/System/Random.hs b/System/Random.hs index 8c40e8af0..78dda7654 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -40,6 +40,72 @@ -- instance of 'Random' allows one to generate random values of type -- 'Float'. -- +-- [/Example for RNG Implementors:/] +-- +-- For example, we can define a [linear congruential +-- generator](https://en.wikipedia.org/wiki/Linear_congruential_generator): +-- +-- >>> let lcg = (\n -> (n * 1103515245 + 12345) `mod` 2^31) :: Word32 -> Word32 +-- >>> lcg 42 +-- 1250496027 +-- +-- And then make it an instance of `RandomGen`: +-- +-- >>> data LcgGen = LcgGen Word32 +-- +-- >>> :{ +-- instance RandomGen LcgGen where +-- next (LcgGen w) = (fromIntegral x, LcgGen x) +-- where +-- x = lcg w +-- split (LcgGen w) = (LcgGen x, LcgGen y) +-- where +-- x = lcg w +-- y = lcg x +-- :} +-- +-- >>> :{ +-- let randomListM :: (Random a, MonadRandom g m, Num a, Uniform a) => g -> Int -> m [a] +-- randomListM gen n = replicateM n (uniform gen) +-- :} +-- +-- >>> :{ +-- let rolls :: [Word32] +-- rolls = runStateGen_ +-- (LcgGen 1729 :: LcgGen) +-- (randomListM PureGen 10 >>= \xs -> return $ map ((+1) . (`mod` 6)) xs) +-- :} +-- +-- >>> rolls +-- [1,6,5,2,3,2,5,2,3,2] +-- +-- Importantly, this implementation will not be as efficient as it +-- could be because the random values are converted to 'Integer' and +-- then to desired type. +-- +-- Instead we should define (where @unBuildWord32 :: Word32 -> +-- (Word16, Word16)@ is a function to pull apart a 'Word32' into a +-- pair of 'Word16'): +-- +-- >>> data LcgGen' = LcgGen' Word32 +-- +-- >>> :set -fno-warn-missing-methods +-- +-- >>> :{ +-- instance RandomGen LcgGen' where +-- genWord16 (LcgGen' w) = (y, LcgGen' x) +-- where +-- x = lcg w +-- (y, _) = unBuildWord32 x +-- genWord32 (LcgGen' w) = (x, LcgGen' x) +-- where +-- x = lcg w +-- split (LcgGen' w) = (LcgGen' x, LcgGen' y) +-- where +-- x = lcg w +-- y = lcg x +-- :} +-- -- This implementation uses the SplitMix algorithm [1]. -- ----------------------------------------------------------------------------- @@ -134,6 +200,21 @@ import GHC.ForeignPtr import System.IO.Unsafe (unsafePerformIO) import qualified System.Random.SplitMix as SM +-- $setup +-- >>> import Foreign.Marshal.Alloc (alloca) +-- >>> import Foreign.Storable (peekByteOff, pokeByteOff) +-- >>> import System.IO.Unsafe (unsafePerformIO) +-- >>> :{ +-- unBuildWord32 :: Word32 -> (Word16, Word16) +-- unBuildWord32 w = unsafePerformIO $ alloca f +-- where +-- f :: Ptr Word16 -> IO (Word16, Word16) +-- f p = do pokeByteOff p 0 w +-- w0 <- peekByteOff p 0 +-- w1 <- peekByteOff p 1 +-- return (w0, w1) +-- :} + #if !MIN_VERSION_primitive(0,7,0) import Data.Primitive.Types (Addr(..)) @@ -149,6 +230,10 @@ mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 -- | The class 'RandomGen' provides a common interface to random number -- generators. -- +-- N.B. Using 'next' is inefficient as all operations go via +-- 'Integer'. See +-- [here](https://alexey.kuleshevi.ch/blog/2019/12/21/random-benchmarks) +-- for more details. class RandomGen g where -- |The 'next' operation returns an 'Int' that is uniformly distributed -- in the range returned by 'genRange' (including both end points), diff --git a/random.cabal b/random.cabal index 638ddfdda..5b2c032c2 100644 --- a/random.cabal +++ b/random.cabal @@ -12,23 +12,21 @@ description: This package provides a basic random number generation library, including the ability to split random number generators. - extra-source-files: README.md CHANGELOG.md +build-type: Custom +cabal-version: >=1.10 - - -build-type: Simple --- cabal-version 1.8 needed because "the field 'build-depends: random' refers --- to a library which is defined within the same package" -cabal-version: >= 1.8 - - +custom-setup + setup-depends: + base + , Cabal + , cabal-doctest >=1.0.6 Library - exposed-modules: - System.Random + exposed-modules: System.Random + default-language: Haskell2010 ghc-options: -Wall build-depends: base >= 3 && < 5 , bytestring @@ -60,3 +58,12 @@ Test-Suite TestRandomIOs hs-source-dirs: tests build-depends: base >= 3 && < 5, random ghc-options: -rtsopts -O2 -with-rtsopts=-M1M + +Test-Suite doctests + type: exitcode-stdio-1.0 + hs-source-dirs: tests + main-is: doctests.hs + build-depends: base + , doctest >=0.15 + , random + default-language: Haskell2010 \ No newline at end of file diff --git a/tests/doctests.hs b/tests/doctests.hs new file mode 100644 index 000000000..bbf7787f8 --- /dev/null +++ b/tests/doctests.hs @@ -0,0 +1,12 @@ +module Main where + +import Build_doctests (flags, pkgs, module_sources) +import Data.Foldable (traverse_) +import Test.DocTest (doctest) + +main :: IO () +main = do + traverse_ putStrLn args + doctest args + where + args = flags ++ pkgs ++ module_sources From a68020053d533571f82be938470ff38b8d4f96fa Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 6 Mar 2020 16:19:35 +0100 Subject: [PATCH 023/170] Deprecate next --- System/Random.hs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 78dda7654..da9d61b2a 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -11,6 +11,8 @@ {-# LANGUAGE Trustworthy #-} #endif +#include "MachDeps.h" + ----------------------------------------------------------------------------- -- | -- Module : System.Random @@ -238,8 +240,22 @@ class RandomGen g where -- |The 'next' operation returns an 'Int' that is uniformly distributed -- in the range returned by 'genRange' (including both end points), -- and a new generator. - next :: g -> (Int, g) - -- `next` can be deprecated over time + {-# DEPRECATE next "Use genWord32[R] or genWord64[R]" #-} + next :: g -> (Int, g) + next g = (minR + fromIntegral w, g') where + (minR, maxR) = genRange g + range = fromIntegral $ maxR - minR + -- https://hackage.haskell.org/package/ghc-prim-0.5.3/docs/GHC-Prim.html#g:1 + -- GHC always implements Int using the primitive type Int#, whose size + -- equals the MachDeps.h constant WORD_SIZE_IN_BITS. [...] Currently GHC + -- itself has only 32-bit and 64-bit variants [...]. +#if WORD_SIZE_IN_BITS == 32 + (w, g') = genWord32R range g +#elif WORD_SIZE_IN_BITS == 64 + (w, g') = genWord64R range g +#else +# error unsupported WORD_SIZE_IN_BITS +#endif genWord8 :: g -> (Word8, g) genWord8 = first fromIntegral . genWord32R (fromIntegral (maxBound :: Word8)) From 18239f9429afd9260585ccc7ac153b4903690851 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Mon, 9 Mar 2020 10:57:58 +0100 Subject: [PATCH 024/170] Fix overflow --- System/Random.hs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index da9d61b2a..fe4f55fbf 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -244,16 +244,16 @@ class RandomGen g where next :: g -> (Int, g) next g = (minR + fromIntegral w, g') where (minR, maxR) = genRange g - range = fromIntegral $ maxR - minR - -- https://hackage.haskell.org/package/ghc-prim-0.5.3/docs/GHC-Prim.html#g:1 - -- GHC always implements Int using the primitive type Int#, whose size - -- equals the MachDeps.h constant WORD_SIZE_IN_BITS. [...] Currently GHC - -- itself has only 32-bit and 64-bit variants [...]. + range = minR `unsignedDiff` maxR #if WORD_SIZE_IN_BITS == 32 (w, g') = genWord32R range g #elif WORD_SIZE_IN_BITS == 64 (w, g') = genWord64R range g #else +-- https://hackage.haskell.org/package/ghc-prim-0.5.3/docs/GHC-Prim.html#g:1 +-- GHC always implements Int using the primitive type Int#, whose size equals +-- the MachDeps.h constant WORD_SIZE_IN_BITS. [...] Currently GHC itself has +-- only 32-bit and 64-bit variants [...]. # error unsupported WORD_SIZE_IN_BITS #endif @@ -306,6 +306,19 @@ class RandomGen g where -- generators. split :: g -> (g, g) +-- | x `unsignedDiff` y is the difference between x and y as an unsigned number. +unsignedDiff :: Integral a => Int -> Int -> a +unsignedDiff x y + | y < x = unsignedDiff y x + | x < 0 && y < 0 = x' - y' + | x < 0 {- && y >= 0 (implied) -} = x' + y' + | otherwise {- x >= 0 && y >= 0 (implied) -} = y' - x' + where + -- 'fromIntegral' conversion to unsigned type preserves magnitude but not + -- sign so x' and y' are implicitly the absolute values of x and y, + -- respectively + x' = fromIntegral x + y' = fromIntegral y class Monad m => MonadRandom g m where type Seed g :: * From c0a0204e73d10072aa8909a389fc1c93609b63da Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Mon, 9 Mar 2020 11:07:41 +0100 Subject: [PATCH 025/170] Revert "Fix overflow" This reverts commit 67952a4f8f462a2ff129af967a7eb16b77a2e2c7. --- System/Random.hs | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index fe4f55fbf..da9d61b2a 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -244,16 +244,16 @@ class RandomGen g where next :: g -> (Int, g) next g = (minR + fromIntegral w, g') where (minR, maxR) = genRange g - range = minR `unsignedDiff` maxR + range = fromIntegral $ maxR - minR + -- https://hackage.haskell.org/package/ghc-prim-0.5.3/docs/GHC-Prim.html#g:1 + -- GHC always implements Int using the primitive type Int#, whose size + -- equals the MachDeps.h constant WORD_SIZE_IN_BITS. [...] Currently GHC + -- itself has only 32-bit and 64-bit variants [...]. #if WORD_SIZE_IN_BITS == 32 (w, g') = genWord32R range g #elif WORD_SIZE_IN_BITS == 64 (w, g') = genWord64R range g #else --- https://hackage.haskell.org/package/ghc-prim-0.5.3/docs/GHC-Prim.html#g:1 --- GHC always implements Int using the primitive type Int#, whose size equals --- the MachDeps.h constant WORD_SIZE_IN_BITS. [...] Currently GHC itself has --- only 32-bit and 64-bit variants [...]. # error unsupported WORD_SIZE_IN_BITS #endif @@ -306,19 +306,6 @@ class RandomGen g where -- generators. split :: g -> (g, g) --- | x `unsignedDiff` y is the difference between x and y as an unsigned number. -unsignedDiff :: Integral a => Int -> Int -> a -unsignedDiff x y - | y < x = unsignedDiff y x - | x < 0 && y < 0 = x' - y' - | x < 0 {- && y >= 0 (implied) -} = x' + y' - | otherwise {- x >= 0 && y >= 0 (implied) -} = y' - x' - where - -- 'fromIntegral' conversion to unsigned type preserves magnitude but not - -- sign so x' and y' are implicitly the absolute values of x and y, - -- respectively - x' = fromIntegral x - y' = fromIntegral y class Monad m => MonadRandom g m where type Seed g :: * From f4087faf09ccee35cf3a0adda62b5c491d2bcec8 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Mon, 9 Mar 2020 11:18:20 +0100 Subject: [PATCH 026/170] Formatting --- System/Random.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index da9d61b2a..4ae641fbf 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -236,24 +236,24 @@ mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 -- 'Integer'. See -- [here](https://alexey.kuleshevi.ch/blog/2019/12/21/random-benchmarks) -- for more details. +{-# DEPRECATED next "Use genWord32[R] or genWord64[R]" #-} class RandomGen g where -- |The 'next' operation returns an 'Int' that is uniformly distributed -- in the range returned by 'genRange' (including both end points), -- and a new generator. - {-# DEPRECATE next "Use genWord32[R] or genWord64[R]" #-} next :: g -> (Int, g) next g = (minR + fromIntegral w, g') where (minR, maxR) = genRange g range = fromIntegral $ maxR - minR - -- https://hackage.haskell.org/package/ghc-prim-0.5.3/docs/GHC-Prim.html#g:1 - -- GHC always implements Int using the primitive type Int#, whose size - -- equals the MachDeps.h constant WORD_SIZE_IN_BITS. [...] Currently GHC - -- itself has only 32-bit and 64-bit variants [...]. #if WORD_SIZE_IN_BITS == 32 (w, g') = genWord32R range g #elif WORD_SIZE_IN_BITS == 64 (w, g') = genWord64R range g #else +-- https://hackage.haskell.org/package/ghc-prim-0.5.3/docs/GHC-Prim.html#g:1 +-- GHC always implements Int using the primitive type Int#, whose size equals +-- the MachDeps.h constant WORD_SIZE_IN_BITS. [...] Currently GHC itself has +-- only 32-bit and 64-bit variants [...]. # error unsupported WORD_SIZE_IN_BITS #endif From 6f67dd04aed1ec1a58a45fea2039d1fe0d34d6d2 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Thu, 12 Mar 2020 14:16:15 +0000 Subject: [PATCH 027/170] Fix comments in https://github.com/idontgetoutmuch/random/pull/27 --- System/Random.hs | 140 +++++++++++++++++++++++++++-------------------- random.cabal | 2 +- 2 files changed, 82 insertions(+), 60 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 4ae641fbf..1f678dd06 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -42,73 +42,93 @@ -- instance of 'Random' allows one to generate random values of type -- 'Float'. -- --- [/Example for RNG Implementors:/] --- --- For example, we can define a [linear congruential --- generator](https://en.wikipedia.org/wiki/Linear_congruential_generator): +-- This implementation uses the SplitMix algorithm [1]. -- --- >>> let lcg = (\n -> (n * 1103515245 + 12345) `mod` 2^31) :: Word32 -> Word32 --- >>> lcg 42 --- 1250496027 +-- [/Example for RNG Implementors:/] -- --- And then make it an instance of `RandomGen`: +-- Suppose you want to use a [permuted congruential +-- generator](https://en.wikipedia.org/wiki/Permuted_congruential_generator) +-- as the source of entropy (FIXME: is that the correct +-- terminology). You can make it an instance of `RandomGen`: -- --- >>> data LcgGen = LcgGen Word32 +-- >>> data PCGen = PCGen !Word64 !Word64 -- -- >>> :{ --- instance RandomGen LcgGen where --- next (LcgGen w) = (fromIntegral x, LcgGen x) --- where --- x = lcg w --- split (LcgGen w) = (LcgGen x, LcgGen y) --- where --- x = lcg w --- y = lcg x +-- let stepGen :: PCGen -> (Word32, PCGen) +-- stepGen (PCGen state inc) = let +-- newState = state * 6364136223846793005 + (inc .|. 1) +-- xorShifted = fromIntegral (((state `shiftR` 18) `xor` state) `shiftR` 27) :: Word32 +-- rot = fromIntegral (state `shiftR` 59) :: Word32 +-- out = (xorShifted `shiftR` (fromIntegral rot)) .|. (xorShifted `shiftL` fromIntegral ((-rot) .&. 31)) +-- in (out, PCGen newState inc) -- :} -- --- >>> :{ --- let randomListM :: (Random a, MonadRandom g m, Num a, Uniform a) => g -> Int -> m [a] --- randomListM gen n = replicateM n (uniform gen) --- :} +-- >>> fst $ stepGen $ snd $ stepGen (PCGen 17 29) +-- 3288430965 -- -- >>> :{ --- let rolls :: [Word32] --- rolls = runStateGen_ --- (LcgGen 1729 :: LcgGen) --- (randomListM PureGen 10 >>= \xs -> return $ map ((+1) . (`mod` 6)) xs) +-- instance RandomGen PCGen where +-- next g = (fromIntegral y, h) +-- where +-- (y, h) = stepGen g +-- split _ = error "This PRNG is not splittable" -- :} -- --- >>> rolls --- [1,6,5,2,3,2,5,2,3,2] --- -- Importantly, this implementation will not be as efficient as it -- could be because the random values are converted to 'Integer' and -- then to desired type. -- --- Instead we should define (where @unBuildWord32 :: Word32 -> +-- Instead we should define (where e.g. @unBuildWord32 :: Word32 -> -- (Word16, Word16)@ is a function to pull apart a 'Word32' into a -- pair of 'Word16'): -- --- >>> data LcgGen' = LcgGen' Word32 +-- >>> data PCGen' = PCGen' !Word64 !Word64 -- -- >>> :set -fno-warn-missing-methods -- -- >>> :{ --- instance RandomGen LcgGen' where --- genWord16 (LcgGen' w) = (y, LcgGen' x) +-- instance RandomGen PCGen' where +-- genWord8 (PCGen' s i) = (z, PCGen' s' i') -- where --- x = lcg w --- (y, _) = unBuildWord32 x --- genWord32 (LcgGen' w) = (x, LcgGen' x) +-- (x, PCGen s' i') = stepGen (PCGen s i) +-- y = fst $ unBuildWord32 x +-- z = fst $ unBuildWord16 y +-- genWord16 (PCGen' s i) = (y, PCGen' s' i') -- where --- x = lcg w --- split (LcgGen' w) = (LcgGen' x, LcgGen' y) +-- (x, PCGen s' i') = stepGen (PCGen s i) +-- y = fst $ unBuildWord32 x +-- genWord32 (PCGen' s i) = (x, PCGen' s' i') -- where --- x = lcg w --- y = lcg x +-- (x, PCGen s' i') = stepGen (PCGen s i) +-- genWord64 (PCGen' s i) = (undefined, PCGen' s i) +-- where +-- (x, g) = stepGen (PCGen s i) +-- (y, PCGen s' i') = stepGen g +-- split _ = error "This PRNG is not splittable" -- :} -- --- This implementation uses the SplitMix algorithm [1]. +-- [/Example for RNG Users:/] +-- +-- Suppose you want to simulate rolls from a dice (yes I know it's a +-- plural form but it's now common to use it as a singular form): +-- +-- >>> :{ +-- let randomListM :: (Random a, MonadRandom g m, Num a, Uniform a) => g -> Int -> m [a] +-- randomListM gen n = replicateM n (uniform gen) +-- :} +-- +-- >>> :{ +-- let rolls :: [Word32] +-- rolls = runStateGen_ +-- (PCGen 17 29) +-- (randomListM PureGen 10 >>= \xs -> return $ map ((+1) . (`mod` 6)) xs) +-- :} +-- +-- >>> rolls +-- [1,4,2,4,2,2,3,1,5,1] +-- +-- FIXME: What should we say about generating values from types other +-- than Word8 etc? -- ----------------------------------------------------------------------------- @@ -203,18 +223,21 @@ import System.IO.Unsafe (unsafePerformIO) import qualified System.Random.SplitMix as SM -- $setup --- >>> import Foreign.Marshal.Alloc (alloca) --- >>> import Foreign.Storable (peekByteOff, pokeByteOff) --- >>> import System.IO.Unsafe (unsafePerformIO) -- >>> :{ -- unBuildWord32 :: Word32 -> (Word16, Word16) --- unBuildWord32 w = unsafePerformIO $ alloca f --- where --- f :: Ptr Word16 -> IO (Word16, Word16) --- f p = do pokeByteOff p 0 w --- w0 <- peekByteOff p 0 --- w1 <- peekByteOff p 1 --- return (w0, w1) +-- unBuildWord32 w = (fromIntegral (shiftR w 16), +-- fromIntegral (fromIntegral (maxBound :: Word16) .&. w)) +-- :} +-- +-- >>> :{ +-- unBuildWord16 :: Word16 -> (Word8, Word8) +-- unBuildWord16 w = (fromIntegral (shiftR w 8), +-- fromIntegral (fromIntegral (maxBound :: Word8) .&. w)) +-- :} +-- +-- >>> :{ +-- buildWord64 :: Word32 -> Word32 -> Word64 +-- buildWord64 w0 w1 = ((fromIntegral w1) `shiftL` 32) .|. (fromIntegral w0) -- :} #if !MIN_VERSION_primitive(0,7,0) @@ -231,16 +254,16 @@ mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 -- | The class 'RandomGen' provides a common interface to random number -- generators. --- --- N.B. Using 'next' is inefficient as all operations go via --- 'Integer'. See --- [here](https://alexey.kuleshevi.ch/blog/2019/12/21/random-benchmarks) --- for more details. {-# DEPRECATED next "Use genWord32[R] or genWord64[R]" #-} class RandomGen g where - -- |The 'next' operation returns an 'Int' that is uniformly distributed - -- in the range returned by 'genRange' (including both end points), - -- and a new generator. + -- |The 'next' operation returns an 'Int' that is uniformly + -- distributed in the range returned by 'genRange' (including both + -- end points), and a new generator. Using 'next' is inefficient as + -- all operations go via 'Integer'. See + -- [here](https://alexey.kuleshevi.ch/blog/2019/12/21/random-benchmarks) + -- for more details. It is thus deprecated. If you need random + -- values from other types you will need to construct a suitable + -- conversion function. next :: g -> (Int, g) next g = (minR + fromIntegral w, g') where (minR, maxR) = genRange g @@ -279,7 +302,6 @@ class RandomGen g where genByteArray n g = runPureGenST g $ uniformByteArrayPrim n {-# INLINE genByteArray #-} - -- |The 'genRange' operation yields the range of values returned by -- the generator. -- diff --git a/random.cabal b/random.cabal index 5b2c032c2..d1b6d0d07 100644 --- a/random.cabal +++ b/random.cabal @@ -66,4 +66,4 @@ Test-Suite doctests build-depends: base , doctest >=0.15 , random - default-language: Haskell2010 \ No newline at end of file + default-language: Haskell2010 From f809609bd76a37b39c1cdc906efdd37d936799fc Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 12 Mar 2020 20:06:24 +0100 Subject: [PATCH 028/170] Add property tests --- System/Random.hs | 14 +- random.cabal | 126 ++++++++++-------- tests/Legacy.hs | 17 +++ tests/{random1283.hs => Legacy/Random1283.hs} | 2 + tests/{rangeTest.hs => Legacy/RangeTest.hs} | 67 +++++----- tests/{ => Legacy}/T7936.hs | 2 +- tests/{ => Legacy}/TestRandomIOs.hs | 2 +- tests/{ => Legacy}/TestRandomRs.hs | 2 +- tests/Makefile | 14 -- tests/Spec.hs | 50 +++++++ tests/Spec/Bitmask.hs | 19 +++ tests/Spec/Range.hs | 19 +++ tests/all.T | 10 -- tests/rangeTest.stdout | 67 ---------- tests/slowness.hs | 55 -------- 15 files changed, 223 insertions(+), 243 deletions(-) create mode 100644 tests/Legacy.hs rename tests/{random1283.hs => Legacy/Random1283.hs} (96%) rename tests/{rangeTest.hs => Legacy/RangeTest.hs} (61%) rename tests/{ => Legacy}/T7936.hs (91%) rename tests/{ => Legacy}/TestRandomIOs.hs (94%) rename tests/{ => Legacy}/TestRandomRs.hs (95%) delete mode 100644 tests/Makefile create mode 100644 tests/Spec.hs create mode 100644 tests/Spec/Bitmask.hs create mode 100644 tests/Spec/Range.hs delete mode 100644 tests/all.T delete mode 100644 tests/rangeTest.stdout delete mode 100644 tests/slowness.hs diff --git a/System/Random.hs b/System/Random.hs index 1f678dd06..75e1ce737 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -195,6 +195,8 @@ module System.Random -- * References -- $references + -- * Internals + , bitmaskWithRejection -- FIXME Export this in a better way, e.g. in System.Random.Impl or something like that ) where import Control.Arrow @@ -255,6 +257,7 @@ mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 -- | The class 'RandomGen' provides a common interface to random number -- generators. {-# DEPRECATED next "Use genWord32[R] or genWord64[R]" #-} +{-# DEPRECATED genRange "Use genWord32[R] or genWord64[R]" #-} class RandomGen g where -- |The 'next' operation returns an 'Int' that is uniformly -- distributed in the range returned by 'genRange' (including both @@ -307,7 +310,7 @@ class RandomGen g where -- -- It is required that: -- - -- * If @(a,b) = 'genRange' g@, then @a < b@. + -- * If @(a, b) = 'genRange' g@, then @a <= b@. -- -- * 'genRange' always returns a pair of defined 'Int's. -- @@ -319,15 +322,12 @@ class RandomGen g where -- a different range to the generator passed to 'next'. -- -- The default definition spans the full range of 'Int'. - genRange :: g -> (Int,Int) - - -- default method + genRange :: g -> (Int, Int) genRange _ = (minBound, maxBound) - -- |The 'split' operation allows one to obtain two distinct random number + -- | The 'split' operation allows one to obtain two distinct random number -- generators. - split :: g -> (g, g) - + split :: g -> (g, g) class Monad m => MonadRandom g m where type Seed g :: * diff --git a/random.cabal b/random.cabal index d1b6d0d07..027977740 100644 --- a/random.cabal +++ b/random.cabal @@ -1,69 +1,83 @@ -name: random -version: 1.2 - - -license: BSD3 -license-file: LICENSE -maintainer: core-libraries-committee@haskell.org -bug-reports: https://github.com/haskell/random/issues -synopsis: random number library -category: System +cabal-version: >=1.10 +name: random +version: 1.2 +license: BSD3 +license-file: LICENSE +maintainer: core-libraries-committee@haskell.org +bug-reports: https://github.com/haskell/random/issues +synopsis: random number library description: - This package provides a basic random number generation - library, including the ability to split random number - generators. + This package provides a basic random number generation + library, including the ability to split random number + generators. + +category: System +build-type: Custom extra-source-files: - README.md - CHANGELOG.md -build-type: Custom -cabal-version: >=1.10 + README.md + CHANGELOG.md + +source-repository head + type: git + location: http://git.haskell.org/packages/random.git custom-setup - setup-depends: - base - , Cabal - , cabal-doctest >=1.0.6 + setup-depends: + base -any, + Cabal -any, + cabal-doctest >=1.0.6 -Library - exposed-modules: System.Random +library + exposed-modules: System.Random default-language: Haskell2010 - ghc-options: -Wall - build-depends: base >= 3 && < 5 - , bytestring - , primitive - , mtl - , splitmix + ghc-options: -Wall + build-depends: + base >=3 && <5, + bytestring -any, + primitive -any, + mtl -any, + splitmix -any -source-repository head - type: git - location: http://git.haskell.org/packages/random.git +test-suite legacy + type: exitcode-stdio-1.0 + main-is: Legacy.hs + hs-source-dirs: tests + other-modules: + Legacy.T7936 + Legacy.TestRandomIOs + Legacy.TestRandomRs + Legacy.Random1283 + Legacy.RangeTest -Test-Suite T7936 - type: exitcode-stdio-1.0 - main-is: T7936.hs - hs-source-dirs: tests - build-depends: base >= 3 && < 5, random - ghc-options: -rtsopts -O2 + default-language: Haskell2010 + ghc-options: -with-rtsopts=-M4M + build-depends: + base -any, + containers -any, + random -any -Test-Suite TestRandomRs - type: exitcode-stdio-1.0 - main-is: TestRandomRs.hs - hs-source-dirs: tests - build-depends: base >= 3 && < 5, random - ghc-options: -rtsopts -O2 -with-rtsopts=-M1M +test-suite doctests + type: exitcode-stdio-1.0 + main-is: doctests.hs + hs-source-dirs: tests + default-language: Haskell2010 + build-depends: + base -any, + doctest >=0.15, + random -any -Test-Suite TestRandomIOs +test-suite spec type: exitcode-stdio-1.0 - main-is: TestRandomIOs.hs + main-is: Spec.hs hs-source-dirs: tests - build-depends: base >= 3 && < 5, random - ghc-options: -rtsopts -O2 -with-rtsopts=-M1M + other-modules: + Spec.Bitmask + Spec.Range -Test-Suite doctests - type: exitcode-stdio-1.0 - hs-source-dirs: tests - main-is: doctests.hs - build-depends: base - , doctest >=0.15 - , random - default-language: Haskell2010 + ghc-options: -Wall + build-depends: + base -any, + random -any, + tasty -any, + tasty-smallcheck -any, + tasty-expected-failure -any diff --git a/tests/Legacy.hs b/tests/Legacy.hs new file mode 100644 index 000000000..260743db2 --- /dev/null +++ b/tests/Legacy.hs @@ -0,0 +1,17 @@ +module Main (main) where + +import qualified Legacy.Random1283 as Random1283 +import qualified Legacy.RangeTest as RangeTest +import qualified Legacy.T7936 as T7936 +import qualified Legacy.TestRandomIOs as TestRandomIOs +-- FIXME Implement 'instance UniformRange Integer', then uncomment this import. +-- import qualified Legacy.TestRandomRs as TestRandomRs + +main :: IO () +main = do + Random1283.main + RangeTest.main + T7936.main + TestRandomIOs.main + -- FIXME Implement 'instance UniformRange Integer', then uncomment TestRandomRs. + -- TestRandomRs.main diff --git a/tests/random1283.hs b/tests/Legacy/Random1283.hs similarity index 96% rename from tests/random1283.hs rename to tests/Legacy/Random1283.hs index 33fc48862..c1aba38ef 100644 --- a/tests/random1283.hs +++ b/tests/Legacy/Random1283.hs @@ -1,3 +1,5 @@ +module Legacy.Random1283 (main) where + import Control.Concurrent import Control.Monad hiding (empty) import Data.Sequence (ViewL(..), empty, fromList, viewl, (<|), (|>), (><)) diff --git a/tests/rangeTest.hs b/tests/Legacy/RangeTest.hs similarity index 61% rename from tests/rangeTest.hs rename to tests/Legacy/RangeTest.hs index ac62c71dd..6cb957181 100644 --- a/tests/rangeTest.hs +++ b/tests/Legacy/RangeTest.hs @@ -1,4 +1,7 @@ {-# LANGUAGE CPP #-} + +module Legacy.RangeTest (main) where + import Control.Monad import System.Random import Data.Int @@ -56,8 +59,9 @@ trials = 5000 main = do checkBounds "Int" boundedRange (approxBounds random trials (undefined::Int)) - checkBounds "Integer" (False, fromIntegral (minBound::Int), fromIntegral (maxBound::Int)) - (approxBounds random trials (undefined::Integer)) + -- FIXME Implement the relevant UniformRange instances and uncomment the tests below. + --checkBounds "Integer" (False, fromIntegral (minBound::Int), fromIntegral (maxBound::Int)) + -- (approxBounds random trials (undefined::Integer)) checkBounds "Int8" boundedRange (approxBounds random trials (undefined::Int8)) checkBounds "Int16" boundedRange (approxBounds random trials (undefined::Int16)) checkBounds "Int32" boundedRange (approxBounds random trials (undefined::Int32)) @@ -91,16 +95,17 @@ main = checkBounds "CUIntMax" boundedRange (approxBounds random trials (undefined:: CUIntMax)) -- Then check all the range-restricted versions: - checkBounds "Int R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int)) - checkBounds "Integer R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Integer)) - checkBounds "Int8 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int8)) - checkBounds "Int8 Rsmall" (False,-50,50) (approxBounds (randomR (-50,50)) trials (undefined::Int8)) - checkBounds "Int8 Rmini" (False,3,4) (approxBounds (randomR (3,4)) trials (undefined::Int8)) - checkBounds "Int8 Rtrivial" (False,3,3) (approxBounds (randomR (3,3)) trials (undefined::Int8)) - - checkBounds "Int16 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int16)) - checkBounds "Int32 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int32)) - checkBounds "Int64 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int64)) + -- FIXME Implement the relevant UniformRange instances and uncomment the tests below. + --checkBounds "Int R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int)) + --checkBounds "Integer R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Integer)) + --checkBounds "Int8 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int8)) + --checkBounds "Int8 Rsmall" (False,-50,50) (approxBounds (randomR (-50,50)) trials (undefined::Int8)) + --checkBounds "Int8 Rmini" (False,3,4) (approxBounds (randomR (3,4)) trials (undefined::Int8)) + --checkBounds "Int8 Rtrivial" (False,3,3) (approxBounds (randomR (3,3)) trials (undefined::Int8)) + + --checkBounds "Int16 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int16)) + --checkBounds "Int32 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int32)) + --checkBounds "Int64 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int64)) checkBounds "Word R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined::Word)) checkBounds "Word8 R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined::Word8)) checkBounds "Word16 R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined::Word16)) @@ -109,25 +114,25 @@ main = checkBounds "Double R" (True,10.0,77.0) (approxBounds (randomR (10,77)) trials (undefined::Double)) checkBounds "Float R" (True,10.0,77.0) (approxBounds (randomR (10,77)) trials (undefined::Float)) - checkBounds "CChar R" (False,0,100) (approxBounds (randomR (0,100)) trials (undefined:: CChar)) - checkBounds "CSChar R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CSChar)) - checkBounds "CUChar R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUChar)) - checkBounds "CShort R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CShort)) - checkBounds "CUShort R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUShort)) - checkBounds "CInt R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CInt)) - checkBounds "CUInt R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUInt)) - checkBounds "CLong R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CLong)) - checkBounds "CULong R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CULong)) - checkBounds "CPtrdiff R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CPtrdiff)) - checkBounds "CSize R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CSize)) - checkBounds "CWchar R" (False,0,100) (approxBounds (randomR (0,100)) trials (undefined:: CWchar)) - checkBounds "CSigAtomic R" (False,0,100) (approxBounds (randomR (0,100)) trials (undefined:: CSigAtomic)) - checkBounds "CLLong R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CLLong)) - checkBounds "CULLong R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CULLong)) - checkBounds "CIntPtr R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CIntPtr)) - checkBounds "CUIntPtr R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUIntPtr)) - checkBounds "CIntMax R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CIntMax)) - checkBounds "CUIntMax R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUIntMax)) + --checkBounds "CChar R" (False,0,100) (approxBounds (randomR (0,100)) trials (undefined:: CChar)) + --checkBounds "CSChar R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CSChar)) + --checkBounds "CUChar R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUChar)) + --checkBounds "CShort R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CShort)) + --checkBounds "CUShort R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUShort)) + --checkBounds "CInt R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CInt)) + --checkBounds "CUInt R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUInt)) + --checkBounds "CLong R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CLong)) + --checkBounds "CULong R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CULong)) + --checkBounds "CPtrdiff R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CPtrdiff)) + --checkBounds "CSize R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CSize)) + --checkBounds "CWchar R" (False,0,100) (approxBounds (randomR (0,100)) trials (undefined:: CWchar)) + --checkBounds "CSigAtomic R" (False,0,100) (approxBounds (randomR (0,100)) trials (undefined:: CSigAtomic)) + --checkBounds "CLLong R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CLLong)) + --checkBounds "CULLong R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CULLong)) + --checkBounds "CIntPtr R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CIntPtr)) + --checkBounds "CUIntPtr R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUIntPtr)) + --checkBounds "CIntMax R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CIntMax)) + --checkBounds "CUIntMax R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUIntMax)) -- Untested: -- instance Random Char where diff --git a/tests/T7936.hs b/tests/Legacy/T7936.hs similarity index 91% rename from tests/T7936.hs rename to tests/Legacy/T7936.hs index cfea911bc..0ed0d109b 100644 --- a/tests/T7936.hs +++ b/tests/Legacy/T7936.hs @@ -6,7 +6,7 @@ -- $ cabal test T7936 --test-options="+RTS -M1M -RTS" -- T7936: Heap exhausted; -module Main where +module Legacy.T7936 where import System.Random (newStdGen) import Control.Monad (replicateM_) diff --git a/tests/TestRandomIOs.hs b/tests/Legacy/TestRandomIOs.hs similarity index 94% rename from tests/TestRandomIOs.hs rename to tests/Legacy/TestRandomIOs.hs index d8a00cc7c..b07a993f0 100644 --- a/tests/TestRandomIOs.hs +++ b/tests/Legacy/TestRandomIOs.hs @@ -6,7 +6,7 @@ -- $ cabal test TestRandomIOs --test-options="+RTS -M1M -RTS" -- TestRandomIOs: Heap exhausted; -module Main where +module Legacy.TestRandomIOs where import Control.Monad (replicateM) import System.Random (randomIO) diff --git a/tests/TestRandomRs.hs b/tests/Legacy/TestRandomRs.hs similarity index 95% rename from tests/TestRandomRs.hs rename to tests/Legacy/TestRandomRs.hs index cdae106a6..8a786b5c2 100644 --- a/tests/TestRandomRs.hs +++ b/tests/Legacy/TestRandomRs.hs @@ -10,7 +10,7 @@ -- $ cabal test TestRandomRs --test-options="+RTS -M1M -RTS" -- TestRandomRs: Heap exhausted; -module Main where +module Legacy.TestRandomRs where import Control.Monad (liftM, replicateM) import System.Random (randomRs, getStdGen) diff --git a/tests/Makefile b/tests/Makefile deleted file mode 100644 index 39c71491b..000000000 --- a/tests/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -# This Makefile runs the tests using GHC's testsuite framework. It -# assumes the package is part of a GHC build tree with the testsuite -# installed in ../../../testsuite. - -TOP=../../../testsuite -include $(TOP)/mk/boilerplate.mk -include $(TOP)/mk/test.mk - - -# Build tests locally without the central GHC testing infrastructure: -local: - ghc --make rangeTest.hs - ghc --make random1283.hs - diff --git a/tests/Spec.hs b/tests/Spec.hs new file mode 100644 index 000000000..65d85b315 --- /dev/null +++ b/tests/Spec.hs @@ -0,0 +1,50 @@ +{-# LANGUAGE TypeApplications #-} + +module Main (main) where + +import Data.Word (Word32, Word64) +import System.Random +import Test.Tasty +import Test.Tasty.ExpectedFailure (expectFail) +import Test.Tasty.SmallCheck as SC + +import qualified Spec.Bitmask as Bitmask +import qualified Spec.Bitmask as Range + +main :: IO () +main = defaultMain $ testGroup "Spec" + [ bitmaskSpecWord32, bitmaskSpecWord64 + , rangeSpecWord32, rangeSpecInt + ] + +bitmaskSpecWord32 :: TestTree +bitmaskSpecWord32 = testGroup "bitmaskWithRejection (Word32)" + [ SC.testProperty "symmetric" $ seeded $ Bitmask.symmetric @StdGen @Word32 + , SC.testProperty "bounded" $ seeded $ Bitmask.bounded @StdGen @Word32 + , SC.testProperty "singleton" $ seeded $ Bitmask.singleton @StdGen @Word32 + ] + +bitmaskSpecWord64 :: TestTree +bitmaskSpecWord64 = testGroup "bitmaskWithRejection (Word64)" + [ SC.testProperty "symmetric" $ seeded $ Bitmask.symmetric @StdGen @Word64 + , SC.testProperty "bounded" $ seeded $ Bitmask.bounded @StdGen @Word64 + , SC.testProperty "singleton" $ seeded $ Bitmask.singleton @StdGen @Word64 + ] + +rangeSpecWord32 :: TestTree +rangeSpecWord32 = testGroup "uniformR (Word32)" + [ SC.testProperty "(Word32) symmetric" $ seeded $ Range.symmetric @StdGen @Word32 + , SC.testProperty "(Word32) bounded" $ seeded $ Range.bounded @StdGen @Word32 + , SC.testProperty "(Word32) singleton" $ seeded $ Range.singleton @StdGen @Word32 + ] + +rangeSpecInt :: TestTree +rangeSpecInt = testGroup "uniformR (Int)" + [ SC.testProperty "(Int) symmetric" $ seeded $ Range.symmetric @StdGen @Int + , expectFail $ SC.testProperty "(Int) bounded" $ seeded $ Range.bounded @StdGen @Int + , SC.testProperty "(Int) singleton" $ seeded $ Range.singleton @StdGen @Int + ] + +-- | Create a StdGen instance from an Int and pass it to the given function. +seeded :: (StdGen -> a) -> Int -> a +seeded f = f . mkStdGen diff --git a/tests/Spec/Bitmask.hs b/tests/Spec/Bitmask.hs new file mode 100644 index 000000000..bd43505a0 --- /dev/null +++ b/tests/Spec/Bitmask.hs @@ -0,0 +1,19 @@ +module Spec.Bitmask (symmetric, bounded, singleton) where + +import Data.Bits +import System.Random + +symmetric :: (RandomGen g, FiniteBits a, Num a, Ord a, Random a) => g -> (a, a) -> Bool +symmetric g (l, r) = fst (bitmaskWithRejection (l, r) g) == fst (bitmaskWithRejection (r, l) g) + +bounded :: (RandomGen g, FiniteBits a, Num a, Ord a, Random a) => g -> (a, a) -> Bool +bounded g (l, r) = bottom <= result && result <= top + where + bottom = min l r + top = max l r + result = fst (bitmaskWithRejection (l, r) g) + +singleton :: (RandomGen g, FiniteBits a, Num a, Ord a, Random a) => g -> a -> Bool +singleton g x = result == x + where + result = fst (bitmaskWithRejection (x, x) g) diff --git a/tests/Spec/Range.hs b/tests/Spec/Range.hs new file mode 100644 index 000000000..09a65035a --- /dev/null +++ b/tests/Spec/Range.hs @@ -0,0 +1,19 @@ +module Spec.Range (symmetric, bounded, singleton) where + +import Data.Bits +import System.Random + +symmetric :: (RandomGen g, Random a, Eq a) => g -> (a, a) -> Bool +symmetric g (l, r) = fst (randomR (l, r) g) == fst (randomR (r, l) g) + +bounded :: (RandomGen g, Random a, Ord a) => g -> (a, a) -> Bool +bounded g (l, r) = bottom <= result && result <= top + where + bottom = min l r + top = max l r + result = fst (randomR (l, r) g) + +singleton :: (RandomGen g, Random a, Eq a) => g -> a -> Bool +singleton g x = result == x + where + result = fst (randomR (x, x) g) diff --git a/tests/all.T b/tests/all.T deleted file mode 100644 index f1675ed5c..000000000 --- a/tests/all.T +++ /dev/null @@ -1,10 +0,0 @@ - -test('rangeTest', - normal, - compile_and_run, - ['']) - -test('random1283', - reqlib('containers'), - compile_and_run, - [' -package containers']) diff --git a/tests/rangeTest.stdout b/tests/rangeTest.stdout deleted file mode 100644 index 55ccaffb4..000000000 --- a/tests/rangeTest.stdout +++ /dev/null @@ -1,67 +0,0 @@ -Int: Passed -Integer: Passed -Int8: Passed -Int16: Passed -Int32: Passed -Int64: Passed -Word: Passed -Word8: Passed -Word16: Passed -Word32: Passed -Word64: Passed -Double: Passed -Float: Passed -CChar: Passed -CSChar: Passed -CUChar: Passed -CShort: Passed -CUShort: Passed -CInt: Passed -CUInt: Passed -CLong: Passed -CULong: Passed -CPtrdiff: Passed -CSize: Passed -CWchar: Passed -CSigAtomic: Passed -CLLong: Passed -CULLong: Passed -CIntPtr: Passed -CUIntPtr: Passed -CIntMax: Passed -CUIntMax: Passed -Int R: Passed -Integer R: Passed -Int8 R: Passed -Int8 Rsmall: Passed -Int8 Rmini: Passed -Int8 Rtrivial: Passed -Int16 R: Passed -Int32 R: Passed -Int64 R: Passed -Word R: Passed -Word8 R: Passed -Word16 R: Passed -Word32 R: Passed -Word64 R: Passed -Double R: Passed -Float R: Passed -CChar R: Passed -CSChar R: Passed -CUChar R: Passed -CShort R: Passed -CUShort R: Passed -CInt R: Passed -CUInt R: Passed -CLong R: Passed -CULong R: Passed -CPtrdiff R: Passed -CSize R: Passed -CWchar R: Passed -CSigAtomic R: Passed -CLLong R: Passed -CULLong R: Passed -CIntPtr R: Passed -CUIntPtr R: Passed -CIntMax R: Passed -CUIntMax R: Passed diff --git a/tests/slowness.hs b/tests/slowness.hs deleted file mode 100644 index 162a4cff0..000000000 --- a/tests/slowness.hs +++ /dev/null @@ -1,55 +0,0 @@ - --- http://hackage.haskell.org/trac/ghc/ticket/427 - --- Two (performance) problems in one: - -{-# OPTIONS -fffi #-} -module Main (main) where - -import Control.Monad -import System.Random - -foreign import ccall unsafe "random" _crandom :: IO Int -foreign import ccall unsafe "stdlib.hs" rand :: IO Int - -randomInt :: (Int, Int) -> IO Int -randomInt (min,max) = do --- n <- _crandom - n <- rand - return $ min + n `rem` range - where - range = max - min + 1 - -main = replicateM_ (5*10^6) $ do - x <- randomRIO (0::Int,1000) :: IO Int --- x <- randomInt (0::Int,1000) :: IO Int - x `seq` return () - return () - --- First, without the "seq" at the end, hardly anything is --- evaluated and we're building huge amounts of thunks. --- Three ideas about this one: --- - Blame the user :) --- - data StdGen = StdGen !Int !Int --- Use strict fields in StdGen. Doesn't actually help --- (at least in this example). --- - Force evaluation of the StdGen in getStdRandom. --- Does help in this example, but also changes behaviour --- of the library: --- x <- randomRIO undefined --- currently dies only when x (or the result of a later --- randomRIO) is evaluated. This change causes it to die --- immediately. - --- Second, even _with_ the "seq", replacing "randomRIO" by --- "randomInt" speeds the thing up with a factor of about --- 30. (2 to 3.6, in a "real world" university practicum --- exercise of 900 lines of code) --- Even given the fact that they're not really doing the --- same thing, this seems rather much :( - --------------------------------------------------------------------------------- - --- [2011.06.28] RRN: --- I'm currently seeing 1425 ms vs 43 ms for the above. 33X --- difference. If I use rand() instead it's about 52ms. From f7e89cf0a5882ef04e8a26f8c00bf5ee8b50169b Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 13 Mar 2020 17:23:08 +0100 Subject: [PATCH 029/170] runStateGen_ -> runGenState_ --- System/Random.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/System/Random.hs b/System/Random.hs index 75e1ce737..828f3499d 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -119,7 +119,7 @@ -- -- >>> :{ -- let rolls :: [Word32] --- rolls = runStateGen_ +-- rolls = runGenState_ -- (PCGen 17 29) -- (randomListM PureGen 10 >>= \xs -> return $ map ((+1) . (`mod` 6)) xs) -- :} From 4a31631b33dfc6a73b551f1057a5f4aa21bcc378 Mon Sep 17 00:00:00 2001 From: Alexey Khudyakov Date: Wed, 4 Mar 2020 00:03:32 +0300 Subject: [PATCH 030/170] Add haddocks for Uniform and UniformRange type classes --- System/Random.hs | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 828f3499d..18cb843fa 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -183,6 +183,7 @@ module System.Random , newStdGen -- * Random values of various types + -- $uniform , Uniform(..) , UniformRange(..) , Random(..) @@ -607,22 +608,55 @@ should be likely to produce distinct generators. mkStdGen :: Int -> StdGen -- why not Integer ? mkStdGen s = SM.mkSMGen $ fromIntegral s -{- | -With a source of random number supply in hand, the 'Random' class allows the -programmer to extract random values of a variety of types. -Minimal complete definition: 'randomR' and 'random'. - --} +-- $uniform +-- +-- Type classes 'Uniform' and 'UniformRange' are unrelated type +-- classes. This is deliberate decision because there're types which +-- could have instance for one type class but not the other. +-- +-- For example: 'Integer', 'Float', 'Double' have instance for +-- @UniformRange@ but there's no way to define @Uniform@. +-- +-- Conversely there're types where @Uniform@ instance is possible +-- while @UniformRange@ is not. One example is: +-- +-- > instance (Uniform a, Uniform b) => Uniform (a,b) +-- +-- This instance is very straightforward. @UniformRange@ isn't very +-- sensible even if try to define one compatible with 'Ord' we'll run +-- into problem that we don't know how many values inhabit @b@ +-- therefore we don't even know how many values we need to generate. +-- +-- Another example is points on sphere cirle. Again @Uniform@ is +-- obvious, while @UniformRange@ is impossible since there isn't even +-- order between values. +-- | Generate every possible value for data type with equal probability. class Uniform a where uniform :: MonadRandom g m => g -> m a +-- | Generate every value in provided inclusive range with equal +-- probability. So @uniformR (1,4)@ should generate values from set +-- @[1,2,3,4]@. Inclusive range is used to allow to express any +-- interval for fixed-size ints, enumerations etc. +-- +-- Additionally in order to make function always defined order of +-- elements in range shouldn't matter and following law should hold: +-- +-- > uniformR (a,b) = uniform (b,a) class UniformRange a where uniformR :: MonadRandom g m => (a, a) -> g -> m a +{- | +With a source of random number supply in hand, the 'Random' class allows the +programmer to extract random values of a variety of types. + +Minimal complete definition: 'randomR' and 'random'. + +-} {-# DEPRECATED randomR "In favor of `uniformR`" #-} {-# DEPRECATED randomRIO "In favor of `uniformR`" #-} {-# DEPRECATED randomIO "In favor of `uniformR`" #-} From 23f58a8e443f0447682b38e9870a4a6117df7319 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Sat, 14 Mar 2020 14:08:51 +0000 Subject: [PATCH 031/170] Explain the existence of e.g. runPrimIO --- System/Random.hs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 18cb843fa..4a19a6ee4 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -84,8 +84,6 @@ -- -- >>> data PCGen' = PCGen' !Word64 !Word64 -- --- >>> :set -fno-warn-missing-methods --- -- >>> :{ -- instance RandomGen PCGen' where -- genWord8 (PCGen' s i) = (z, PCGen' s' i') @@ -226,6 +224,8 @@ import System.IO.Unsafe (unsafePerformIO) import qualified System.Random.SplitMix as SM -- $setup +-- >>> :set -XFlexibleContexts +-- >>> :set -fno-warn-missing-methods -- >>> :{ -- unBuildWord32 :: Word32 -> (Word16, Word16) -- unBuildWord32 w = (fromIntegral (shiftR w 16), @@ -521,6 +521,15 @@ runPrimGenST g action = runST $ do runPrimGenST_ :: RandomGen g => g -> (forall s . PrimGen s g -> ST s a) -> a runPrimGenST_ g action = fst $ runPrimGenST g action +-- | Functions like 'runPrimGenIO' are necessary for example if you +-- wish to write a function like +-- +-- >>> let ioGen gen = withBinaryFile "foo.txt" WriteMode $ \h -> ((randomM gen) :: IO Word32) >>= (hPutStr h . show) +-- +-- and then run it +-- +-- >>> runPrimGenIO_ (mkStdGen 1729) ioGen +-- runPrimGenIO :: (RandomGen g, MonadIO m) => g -> (PrimGen RealWorld g -> m a) -> m (a, g) runPrimGenIO g action = do primGen <- liftIO $ restore g From 1ba8fd719398cdaad2f1e892098b4f56571717e9 Mon Sep 17 00:00:00 2001 From: Alexey Khudyakov Date: Wed, 4 Mar 2020 00:03:32 +0300 Subject: [PATCH 032/170] Add haddocks for Uniform and UniformRange type classes --- System/Random.hs | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 828f3499d..18cb843fa 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -183,6 +183,7 @@ module System.Random , newStdGen -- * Random values of various types + -- $uniform , Uniform(..) , UniformRange(..) , Random(..) @@ -607,22 +608,55 @@ should be likely to produce distinct generators. mkStdGen :: Int -> StdGen -- why not Integer ? mkStdGen s = SM.mkSMGen $ fromIntegral s -{- | -With a source of random number supply in hand, the 'Random' class allows the -programmer to extract random values of a variety of types. -Minimal complete definition: 'randomR' and 'random'. - --} +-- $uniform +-- +-- Type classes 'Uniform' and 'UniformRange' are unrelated type +-- classes. This is deliberate decision because there're types which +-- could have instance for one type class but not the other. +-- +-- For example: 'Integer', 'Float', 'Double' have instance for +-- @UniformRange@ but there's no way to define @Uniform@. +-- +-- Conversely there're types where @Uniform@ instance is possible +-- while @UniformRange@ is not. One example is: +-- +-- > instance (Uniform a, Uniform b) => Uniform (a,b) +-- +-- This instance is very straightforward. @UniformRange@ isn't very +-- sensible even if try to define one compatible with 'Ord' we'll run +-- into problem that we don't know how many values inhabit @b@ +-- therefore we don't even know how many values we need to generate. +-- +-- Another example is points on sphere cirle. Again @Uniform@ is +-- obvious, while @UniformRange@ is impossible since there isn't even +-- order between values. +-- | Generate every possible value for data type with equal probability. class Uniform a where uniform :: MonadRandom g m => g -> m a +-- | Generate every value in provided inclusive range with equal +-- probability. So @uniformR (1,4)@ should generate values from set +-- @[1,2,3,4]@. Inclusive range is used to allow to express any +-- interval for fixed-size ints, enumerations etc. +-- +-- Additionally in order to make function always defined order of +-- elements in range shouldn't matter and following law should hold: +-- +-- > uniformR (a,b) = uniform (b,a) class UniformRange a where uniformR :: MonadRandom g m => (a, a) -> g -> m a +{- | +With a source of random number supply in hand, the 'Random' class allows the +programmer to extract random values of a variety of types. + +Minimal complete definition: 'randomR' and 'random'. + +-} {-# DEPRECATED randomR "In favor of `uniformR`" #-} {-# DEPRECATED randomRIO "In favor of `uniformR`" #-} {-# DEPRECATED randomIO "In favor of `uniformR`" #-} From dfd4e2d29dc7cc5a0801cdc41ed3f85264836d94 Mon Sep 17 00:00:00 2001 From: Alexey Khudyakov Date: Sat, 14 Mar 2020 18:12:10 +0300 Subject: [PATCH 033/170] Rewrite explanation for Uniform/UniformRange split --- System/Random.hs | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 18cb843fa..2401a5c2f 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -611,26 +611,27 @@ mkStdGen s = SM.mkSMGen $ fromIntegral s -- $uniform -- --- Type classes 'Uniform' and 'UniformRange' are unrelated type --- classes. This is deliberate decision because there're types which --- could have instance for one type class but not the other. +-- @random@ has two type classes for generation of random numbers: +-- 'Uniform' and 'UniformRange'. One for generating every possible +-- value and another for generating every value in range. In other +-- libraries this functionality frequently bundled into single type +-- class but here we have two type classes because there're types +-- which could have instance for one type class but not the other. -- -- For example: 'Integer', 'Float', 'Double' have instance for -- @UniformRange@ but there's no way to define @Uniform@. -- -- Conversely there're types where @Uniform@ instance is possible --- while @UniformRange@ is not. One example is: --- --- > instance (Uniform a, Uniform b) => Uniform (a,b) --- --- This instance is very straightforward. @UniformRange@ isn't very --- sensible even if try to define one compatible with 'Ord' we'll run --- into problem that we don't know how many values inhabit @b@ --- therefore we don't even know how many values we need to generate. --- --- Another example is points on sphere cirle. Again @Uniform@ is --- obvious, while @UniformRange@ is impossible since there isn't even --- order between values. +-- while @UniformRange@ is not. One example is tuples: @(a,b)@. While +-- @Uniform@ instance is straightforward it's not clear how to define +-- @UniformRange@. We could try to generate values that @a <= x <= b@ +-- But to do that we need to know number of elements of tuple's second +-- type parameter @b@ which we don't have. +-- +-- Or type could have no order at all. Take for example +-- angle. Defining @Uniform@ instance is again straghtforward: just +-- generate value in @[0,2π)@ range. But for any two pair of angles +-- there're two ranges: clockwise and counterclockwise. -- | Generate every possible value for data type with equal probability. From 15c11e3666e0b4273604b5b600a97ef0f7a36339 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Mon, 16 Mar 2020 11:49:55 +0000 Subject: [PATCH 034/170] To test CI works --- System/Random.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/System/Random.hs b/System/Random.hs index 4a19a6ee4..9f82f0a2d 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1,3 +1,4 @@ + {-# LANGUAGE CPP #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} From fdb46b997c71948369d211f02e9cacf3a11d6f8b Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Mon, 16 Mar 2020 11:52:40 +0000 Subject: [PATCH 035/170] Revert "To test CI works" This reverts commit 15c11e3666e0b4273604b5b600a97ef0f7a36339. --- System/Random.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/System/Random.hs b/System/Random.hs index 9f82f0a2d..4a19a6ee4 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1,4 +1,3 @@ - {-# LANGUAGE CPP #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} From 385b1f5bcf1411ca3235428d8865a4917b9c2a29 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Mon, 16 Mar 2020 11:55:47 +0000 Subject: [PATCH 036/170] To test CI --- System/Random.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/System/Random.hs b/System/Random.hs index 828f3499d..57805ee5e 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1,3 +1,4 @@ + {-# LANGUAGE CPP #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} From 616cc476cf5ca665f296736b6bfa0fe943ace4d0 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Mon, 16 Mar 2020 12:08:07 +0000 Subject: [PATCH 037/170] Do we build under 8.6? --- .travis.yml | 5 ++--- System/Random.hs | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6a03bcb12..b89d3d9dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: haskell ghc: - - 7.4 - - 7.6 - - 7.8 + - 8.6 +ma diff --git a/System/Random.hs b/System/Random.hs index 57805ee5e..828f3499d 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1,4 +1,3 @@ - {-# LANGUAGE CPP #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} From f10d6b3fe28e5587baf52f8fa663952790035b86 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Mon, 16 Mar 2020 12:11:22 +0000 Subject: [PATCH 038/170] Fix typo --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b89d3d9dd..8aa53e060 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ language: haskell ghc: - 8.6 -ma From a162206901905a4c51b66938977f7294fe498557 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Mon, 16 Mar 2020 10:27:25 +0100 Subject: [PATCH 039/170] Test with lts-14 and lts-15 on Travis --- .travis.yml | 269 ++++++++++++++++++++++++++++++++++++++++++++++- README.md | 2 +- System/Random.hs | 24 ++--- 3 files changed, 279 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8aa53e060..ff13213b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,266 @@ -language: haskell -ghc: - - 8.6 +############################################################################### +# To see the local changes made to this file, run: +# +# $ curl -sS https://raw.githubusercontent.com/commercialhaskell/stack/0cbcce0b1b89e5431121b745afdd1dfbd3ba8a43/doc/travis-complex.yml | diff -u - .travis.yml +############################################################################### + +# This is the complex Travis configuration, which is intended for use +# on open source libraries which need compatibility across multiple GHC +# versions, must work with cabal-install, and should be +# cross-platform. For more information and other options, see: +# +# https://docs.haskellstack.org/en/stable/travis_ci/ +# +# Copy these contents into the root directory of your Github project in a file +# named .travis.yml + +# Run jobs on Linux unless "os" is specified explicitly. +os: linux + +# Do not choose a language; we provide our own build tools. +language: generic + +# Caching so the next build will be fast too. +cache: + directories: + - $HOME/.ghc + - $HOME/.cabal + - $HOME/.stack + - $TRAVIS_BUILD_DIR/.stack-work + +# The different configurations we want to test. We have BUILD=cabal which uses +# cabal-install, and BUILD=stack which uses Stack. More documentation on each +# of those below. +# +# We set the compiler values here to tell Travis to use a different +# cache file per set of arguments. +# +# If you need to have different apt packages for each combination in the +# job matrix, you can use a line such as: +# addons: {apt: {packages: [libfcgi-dev,libgmp-dev]}} +jobs: + include: + # We grab the appropriate GHC and cabal-install versions from hvr's PPA. See: + # https://github.com/hvr/multi-ghc-travis + #- env: BUILD=cabal GHCVER=7.0.4 CABALVER=1.16 HAPPYVER=1.19.5 ALEXVER=3.1.7 + # compiler: ": #GHC 7.0.4" + # addons: {apt: {packages: [cabal-install-1.16,ghc-7.0.4,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} + #- env: BUILD=cabal GHCVER=7.2.2 CABALVER=1.16 HAPPYVER=1.19.5 ALEXVER=3.1.7 + # compiler: ": #GHC 7.2.2" + # addons: {apt: {packages: [cabal-install-1.16,ghc-7.2.2,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} + #- env: BUILD=cabal GHCVER=7.4.2 CABALVER=1.16 HAPPYVER=1.19.5 ALEXVER=3.1.7 + # compiler: ": #GHC 7.4.2" + # addons: {apt: {packages: [cabal-install-1.16,ghc-7.4.2,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} + #- env: BUILD=cabal GHCVER=7.6.3 CABALVER=1.16 HAPPYVER=1.19.5 ALEXVER=3.1.7 + # compiler: ": #GHC 7.6.3" + # addons: {apt: {packages: [cabal-install-1.16,ghc-7.6.3,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} + #- env: BUILD=cabal GHCVER=7.8.4 CABALVER=1.18 HAPPYVER=1.19.5 ALEXVER=3.1.7 + # compiler: ": #GHC 7.8.4" + # addons: {apt: {packages: [cabal-install-1.18,ghc-7.8.4,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} + #- env: BUILD=cabal GHCVER=7.10.3 CABALVER=1.22 HAPPYVER=1.19.5 ALEXVER=3.1.7 + # compiler: ": #GHC 7.10.3" + # addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.3,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} + #- env: BUILD=cabal GHCVER=8.0.2 CABALVER=1.24 HAPPYVER=1.19.5 ALEXVER=3.1.7 + # compiler: ": #GHC 8.0.2" + # addons: {apt: {packages: [cabal-install-1.24,ghc-8.0.2,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} + #- env: BUILD=cabal GHCVER=8.2.2 CABALVER=2.0 HAPPYVER=1.19.5 ALEXVER=3.1.7 + # compiler: ": #GHC 8.2.2" + # addons: {apt: {packages: [cabal-install-2.0,ghc-8.2.2,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} + #- env: BUILD=cabal GHCVER=8.4.4 CABALVER=2.2 HAPPYVER=1.19.5 ALEXVER=3.1.7 + # compiler: ": #GHC 8.4.4" + # addons: {apt: {packages: [cabal-install-2.2,ghc-8.4.4,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} + #- env: BUILD=cabal GHCVER=8.6.5 CABALVER=2.4 HAPPYVER=1.19.5 ALEXVER=3.1.7 + # compiler: ": #GHC 8.6.5" + # addons: {apt: {packages: [cabal-install-2.4,ghc-8.6.5,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} + + # Build with the newest GHC and cabal-install. This is an accepted failure, + # see below. + #- env: BUILD=cabal GHCVER=head CABALVER=head HAPPYVER=1.19.5 ALEXVER=3.1.7 + # compiler: ": #GHC HEAD" + # addons: {apt: {packages: [cabal-install-head,ghc-head,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} + + # The Stack builds. We can pass in arbitrary Stack arguments via the ARGS + # variable, such as using --stack-yaml to point to a different file. + #- env: BUILD=stack ARGS="" + # compiler: ": #stack default" + # addons: {apt: {packages: [libgmp-dev]}} + + #- env: BUILD=stack ARGS="--resolver lts-2" + # compiler: ": #stack 7.8.4" + # addons: {apt: {packages: [libgmp-dev]}} + + #- env: BUILD=stack ARGS="--resolver lts-3" + # compiler: ": #stack 7.10.2" + # addons: {apt: {packages: [libgmp-dev]}} + + #- env: BUILD=stack ARGS="--resolver lts-6" + # compiler: ": #stack 7.10.3" + # addons: {apt: {packages: [libgmp-dev]}} + + #- env: BUILD=stack ARGS="--resolver lts-7" + # compiler: ": #stack 8.0.1" + # addons: {apt: {packages: [libgmp-dev]}} + + #- env: BUILD=stack ARGS="--resolver lts-9" + # compiler: ": #stack 8.0.2" + # addons: {apt: {packages: [libgmp-dev]}} + + #- env: BUILD=stack ARGS="--resolver lts-11" + # compiler: ": #stack 8.2.2" + # addons: {apt: {packages: [libgmp-dev]}} + + #- env: BUILD=stack ARGS="--resolver lts-12" + # compiler: ": #stack 8.4.4" + # addons: {apt: {packages: [libgmp-dev]}} + + - env: BUILD=stack ARGS="--resolver lts-14" + compiler: ": #stack 8.6.5" + addons: {apt: {packages: [libgmp-dev]}} + + - env: BUILD=stack ARGS="--resolver lts-15" + compiler: ": #stack 8.8.2" + addons: {apt: {packages: [libgmp-dev]}} + + # Nightly builds are allowed to fail + #- env: BUILD=stack ARGS="--resolver nightly" + # compiler: ": #stack nightly" + # addons: {apt: {packages: [libgmp-dev]}} + + # Build on macOS in addition to Linux + #- env: BUILD=stack ARGS="" + # compiler: ": #stack default osx" + # os: osx + + # Travis includes an macOS which is incompatible with GHC 7.8.4 + #- env: BUILD=stack ARGS="--resolver lts-2" + # compiler: ": #stack 7.8.4 osx" + # os: osx + + #- env: BUILD=stack ARGS="--resolver lts-3" + # compiler: ": #stack 7.10.2 osx" + # os: osx + + #- env: BUILD=stack ARGS="--resolver lts-6" + # compiler: ": #stack 7.10.3 osx" + # os: osx + + #- env: BUILD=stack ARGS="--resolver lts-7" + # compiler: ": #stack 8.0.1 osx" + # os: osx + + #- env: BUILD=stack ARGS="--resolver lts-9" + # compiler: ": #stack 8.0.2 osx" + # os: osx + + #- env: BUILD=stack ARGS="--resolver lts-11" + # compiler: ": #stack 8.2.2 osx" + # os: osx + + #- env: BUILD=stack ARGS="--resolver lts-12" + # compiler: ": #stack 8.4.4 osx" + # os: osx + + #- env: BUILD=stack ARGS="--resolver lts-14" + # compiler: ": #stack 8.6.5 osx" + # os: osx + + #- env: BUILD=stack ARGS="--resolver nightly" + # compiler: ": #stack nightly osx" + # os: osx + + allow_failures: + - env: BUILD=cabal GHCVER=head CABALVER=head HAPPYVER=1.19.5 ALEXVER=3.1.7 + - env: BUILD=stack ARGS="--resolver nightly" + +before_install: +# Using compiler above sets CC to an invalid value, so unset it +- unset CC + +# We want to always allow newer versions of packages when building on GHC HEAD +- CABALARGS="" +- if [ "x$GHCVER" = "xhead" ]; then CABALARGS=--allow-newer; fi + +# Download and unpack the stack executable +- export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$HOME/.local/bin:/opt/alex/$ALEXVER/bin:/opt/happy/$HAPPYVER/bin:$HOME/.cabal/bin:$PATH +- mkdir -p ~/.local/bin +- | + if [ `uname` = "Darwin" ] + then + travis_retry curl --insecure -L https://get.haskellstack.org/stable/osx-x86_64.tar.gz | tar xz --strip-components=1 --include '*/stack' -C ~/.local/bin + else + travis_retry curl -L https://get.haskellstack.org/stable/linux-x86_64.tar.gz | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack' + fi + + # Use the more reliable S3 mirror of Hackage + mkdir -p $HOME/.cabal + echo 'remote-repo: hackage.haskell.org:http://hackage.fpcomplete.com/' > $HOME/.cabal/config + echo 'remote-repo-cache: $HOME/.cabal/packages' >> $HOME/.cabal/config + + +install: +- echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]" +- if [ -f configure.ac ]; then autoreconf -i; fi +- | + set -ex + case "$BUILD" in + stack) + # Add in extra-deps for older snapshots, as necessary + # + # This is disabled by default, as relying on the solver like this can + # make builds unreliable. Instead, if you have this situation, it's + # recommended that you maintain multiple stack-lts-X.yaml files. + + #stack --no-terminal --install-ghc $ARGS test --bench --dry-run || ( \ + # stack --no-terminal $ARGS build cabal-install && \ + # stack --no-terminal $ARGS solver --update-config) + + # Build the dependencies + stack --no-terminal --install-ghc $ARGS test --bench --only-dependencies + ;; + cabal) + cabal --version + travis_retry cabal update + + # Get the list of packages from the stack.yaml file. Note that + # this will also implicitly run hpack as necessary to generate + # the .cabal files needed by cabal-install. + PACKAGES=$(stack --install-ghc query locals | grep '^ *path' | sed 's@^ *path:@@') + + cabal install --only-dependencies --enable-tests --enable-benchmarks --force-reinstalls --ghc-options=-O0 --reorder-goals --max-backjumps=-1 $CABALARGS $PACKAGES + ;; + esac + set +ex + +script: +- | + set -ex + case "$BUILD" in + stack) + stack --no-terminal $ARGS test --bench --no-run-benchmarks --haddock --no-haddock-deps + ;; + cabal) + cabal install --enable-tests --enable-benchmarks --force-reinstalls --ghc-options=-O0 --reorder-goals --max-backjumps=-1 $CABALARGS $PACKAGES + + ORIGDIR=$(pwd) + for dir in $PACKAGES + do + cd $dir + cabal check || [ "$CABALVER" == "1.16" ] + cabal sdist + PKGVER=$(cabal info . | awk '{print $2;exit}') + SRC_TGZ=$PKGVER.tar.gz + cd dist + tar zxfv "$SRC_TGZ" + cd "$PKGVER" + cabal configure --enable-tests --ghc-options -O0 + cabal build + if [ "$CABALVER" = "1.16" ] || [ "$CABALVER" = "1.18" ]; then + cabal test + else + cabal test --show-details=streaming --log=/dev/stdout + fi + cd $ORIGDIR + done + ;; + esac + set +ex diff --git a/README.md b/README.md index 9d5bb51b2..04ab37055 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ The Haskell Standard Library -- Random Number Generation ======================================================== -[![Build Status](https://secure.travis-ci.org/haskell/random.svg?branch=master)](http://travis-ci.org/haskell/random) +[![Build Status](https://secure.travis-ci.org/idontgetoutmuch/random.svg?branch=master)](http://travis-ci.org/idontgetoutmuch/random) This library provides a basic interface for (splittable) random number generators. diff --git a/System/Random.hs b/System/Random.hs index 828f3499d..34a721bca 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -224,6 +224,18 @@ import GHC.ForeignPtr import System.IO.Unsafe (unsafePerformIO) import qualified System.Random.SplitMix as SM +#if !MIN_VERSION_primitive(0,7,0) +import Data.Primitive.Types (Addr(..)) + +mutableByteArrayContentsCompat mba = + case mutableByteArrayContents mba of + Addr addr# -> Ptr addr# +#else +mutableByteArrayContentsCompat = mutableByteArrayContents +#endif +mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 +{-# INLINE mutableByteArrayContentsCompat #-} + -- $setup -- >>> :{ -- unBuildWord32 :: Word32 -> (Word16, Word16) @@ -242,18 +254,6 @@ import qualified System.Random.SplitMix as SM -- buildWord64 w0 w1 = ((fromIntegral w1) `shiftL` 32) .|. (fromIntegral w0) -- :} -#if !MIN_VERSION_primitive(0,7,0) -import Data.Primitive.Types (Addr(..)) - -mutableByteArrayContentsCompat mba = - case mutableByteArrayContents mba of - Addr addr# -> Ptr addr# -#else -mutableByteArrayContentsCompat = mutableByteArrayContents -#endif -mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 -{-# INLINE mutableByteArrayContentsCompat #-} - -- | The class 'RandomGen' provides a common interface to random number -- generators. {-# DEPRECATED next "Use genWord32[R] or genWord64[R]" #-} From 6eb11cba34230ef9df62bf9859f484c08365080d Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sat, 14 Mar 2020 21:46:57 +0300 Subject: [PATCH 040/170] Refactor MonadRandom.Seed: * Rename `Seed` -> `Frozen` * `save` -> `freezeGen` * `restore` -> `thawGen` * Convert type to data for better type inference --- System/Random.hs | 92 +++++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 34a721bca..a24331e2a 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -113,7 +113,7 @@ -- plural form but it's now common to use it as a singular form): -- -- >>> :{ --- let randomListM :: (Random a, MonadRandom g m, Num a, Uniform a) => g -> Int -> m [a] +-- let randomListM :: (MonadRandom g m, Num a, Uniform a) => g -> Int -> m [a] -- randomListM gen n = replicateM n (uniform gen) -- :} -- @@ -121,7 +121,7 @@ -- let rolls :: [Word32] -- rolls = runGenState_ -- (PCGen 17 29) --- (randomListM PureGen 10 >>= \xs -> return $ map ((+1) . (`mod` 6)) xs) +-- (randomListM PureGenI 10 >>= \xs -> return $ map ((+1) . (`mod` 6)) xs) -- :} -- -- >>> rolls @@ -141,6 +141,7 @@ module System.Random RandomGen(..) , MonadRandom(..) + , withGenM -- ** Standard random number generators , StdGen , mkStdGen @@ -330,11 +331,11 @@ class RandomGen g where split :: g -> (g, g) class Monad m => MonadRandom g m where - type Seed g :: * - {-# MINIMAL save,restore,(uniformWord32R|uniformWord32),(uniformWord64R|uniformWord64) #-} + data Frozen g :: * + {-# MINIMAL freezeGen,thawGen,(uniformWord32R|uniformWord32),(uniformWord64R|uniformWord64) #-} - restore :: Seed g -> m g - save :: g -> m (Seed g) + thawGen :: Frozen g -> m g + freezeGen :: g -> m (Frozen g) -- | Generate `Word32` up to and including the supplied max value uniformWord32R :: Word32 -> g -> m Word32 uniformWord32R = bitmaskWithRejection32M @@ -356,6 +357,14 @@ class Monad m => MonadRandom g m where {-# INLINE uniformByteArray #-} +withGenM :: MonadRandom g m => Frozen g -> (g -> m a) -> m (a, Frozen g) +withGenM fg action = do + g <- thawGen fg + res <- action g + fg' <- freezeGen g + pure (res, fg') + + -- | This function will efficiently generate a sequence of random bytes in a platform -- independent manner. Memory allocated will be pinned, so it is safe to use for FFI -- calls. @@ -427,17 +436,17 @@ genByteString n g = runPureGenST g (uniformByteStringPrim n) -- -- @since 1.2 runPureGenST :: RandomGen g => g -> (forall s . PureGen g -> StateT g (ST s) a) -> (a, g) -runPureGenST g action = runST $ runGenStateT g $ action PureGen +runPureGenST g action = runST $ runGenStateT g $ action PureGenI {-# INLINE runPureGenST #-} -- | An opaque data type that carries the type of a pure generator -data PureGen g = PureGen +data PureGen g = PureGenI instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) m where - type Seed (PureGen g) = g - restore g = PureGen <$ put g - save _ = get + newtype Frozen (PureGen g) = PureGen g + thawGen (PureGen g) = PureGenI <$ put g + freezeGen _ =fmap PureGen get uniformWord32R r _ = state (genWord32R r) uniformWord64R r _ = state (genWord64R r) uniformWord8 _ = state genWord8 @@ -450,7 +459,7 @@ instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) m where -- -- @since 1.2 genRandom :: (RandomGen g, Random a, MonadState g m) => m a -genRandom = randomM PureGen +genRandom = randomM PureGenI -- | Split current generator and update the state with one part, while returning the other. -- @@ -474,13 +483,13 @@ runGenStateT_ g = fmap fst . flip runStateT g -- It is safe in presence of concurrency since all operations are performed atomically. -- -- @since 1.2 -newtype PrimGen s g = PrimGen (MutVar s g) +newtype PrimGen s g = PrimGenI (MutVar s g) instance (s ~ PrimState m, PrimMonad m, RandomGen g) => MonadRandom (PrimGen s g) m where - type Seed (PrimGen s g) = g - restore = fmap PrimGen . newMutVar - save (PrimGen gVar) = readMutVar gVar + newtype Frozen (PrimGen s g) = PrimGen g + thawGen (PrimGen g) = fmap PrimGenI (newMutVar g) + freezeGen (PrimGenI gVar) = fmap PrimGen (readMutVar gVar) uniformWord32R r = atomicPrimGen (genWord32R r) uniformWord64R r = atomicPrimGen (genWord64R r) uniformWord8 = atomicPrimGen genWord8 @@ -492,7 +501,7 @@ instance (s ~ PrimState m, PrimMonad m, RandomGen g) => -- | Apply a pure operation to generator atomically. atomicPrimGen :: PrimMonad m => (g -> (a, g)) -> PrimGen (PrimState m) g -> m a -atomicPrimGen op (PrimGen gVar) = +atomicPrimGen op (PrimGenI gVar) = atomicModifyMutVar' gVar $ \g -> case op g of (a, g') -> (g', a) @@ -507,13 +516,13 @@ splitPrimGen :: (RandomGen g, PrimMonad m) => PrimGen (PrimState m) g -> m (PrimGen (PrimState m) g) -splitPrimGen = atomicPrimGen split >=> restore +splitPrimGen = atomicPrimGen split >=> thawGen . PrimGen runPrimGenST :: RandomGen g => g -> (forall s . PrimGen s g -> ST s a) -> (a, g) runPrimGenST g action = runST $ do - primGen <- restore g + primGen <- thawGen $ PrimGen g res <- action primGen - g' <- save primGen + PrimGen g' <- freezeGen primGen pure (res, g') -- | Same as `runPrimGenST`, but discard the resulting generator. @@ -522,9 +531,9 @@ runPrimGenST_ g action = fst $ runPrimGenST g action runPrimGenIO :: (RandomGen g, MonadIO m) => g -> (PrimGen RealWorld g -> m a) -> m (a, g) runPrimGenIO g action = do - primGen <- liftIO $ restore g + primGen <- liftIO $ thawGen $ PrimGen g res <- action primGen - g' <- liftIO $ save primGen + PrimGen g' <- liftIO $ freezeGen primGen pure (res, g') {-# INLINE runPrimGenIO #-} @@ -534,16 +543,16 @@ runPrimGenIO_ g action = fst <$> runPrimGenIO g action {-# INLINE runPrimGenIO_ #-} -newtype MutGen s g = MutGen (MutableByteArray s) +newtype MutGen s g = MutGenI (MutableByteArray s) instance (s ~ PrimState m, PrimMonad m, RandomGen g, Prim g) => MonadRandom (MutGen s g) m where - type Seed (MutGen s g) = g - restore g = do + newtype Frozen (MutGen s g) = MutGen g + thawGen (MutGen g) = do ma <- newByteArray (Primitive.sizeOf g) writeByteArray ma 0 g - pure $ MutGen ma - save (MutGen ma) = readByteArray ma 0 + pure $ MutGenI ma + freezeGen (MutGenI ma) = MutGen <$> readByteArray ma 0 uniformWord32R r = applyMutGen (genWord32R r) uniformWord64R r = applyMutGen (genWord64R r) uniformWord8 = applyMutGen genWord8 @@ -553,7 +562,7 @@ instance (s ~ PrimState m, PrimMonad m, RandomGen g, Prim g) => uniformByteArray n = applyMutGen (genByteArray n) applyMutGen :: (Prim g, PrimMonad m) => (g -> (a, g)) -> MutGen (PrimState m) g -> m a -applyMutGen f (MutGen ma) = do +applyMutGen f (MutGenI ma) = do g <- readByteArray ma 0 case f g of (res, g') -> res <$ writeByteArray ma 0 g' @@ -566,13 +575,13 @@ splitMutGen :: (Prim g, RandomGen g, PrimMonad m) => MutGen (PrimState m) g -> m (MutGen (PrimState m) g) -splitMutGen = applyMutGen split >=> restore +splitMutGen = applyMutGen split >=> thawGen . MutGen runMutGenST :: (Prim g, RandomGen g) => g -> (forall s . MutGen s g -> ST s a) -> (a, g) runMutGenST g action = runST $ do - mutGen <- restore g + mutGen <- thawGen $ MutGen g res <- action mutGen - g' <- save mutGen + MutGen g' <- freezeGen mutGen pure (res, g') -- | Same as `runMutGenST`, but discard the resulting generator. @@ -581,9 +590,9 @@ runMutGenST_ g action = fst $ runMutGenST g action runMutGenIO :: (Prim g, RandomGen g, MonadIO m) => g -> (MutGen RealWorld g -> m a) -> m (a, g) runMutGenIO g action = do - mutGen <- liftIO $ restore g + mutGen <- liftIO $ thawGen $ MutGen g res <- action mutGen - g' <- liftIO $ save mutGen + MutGen g' <- liftIO $ freezeGen mutGen pure (res, g') -- | Same as `runMutGenIO`, but discard the resulting generator. @@ -604,18 +613,9 @@ The function 'mkStdGen' provides an alternative way of producing an initial generator, by mapping an 'Int' into a generator. Again, distinct arguments should be likely to produce distinct generators. -} -mkStdGen :: Int -> StdGen -- why not Integer ? +mkStdGen :: Int -> StdGen mkStdGen s = SM.mkSMGen $ fromIntegral s -{- | -With a source of random number supply in hand, the 'Random' class allows the -programmer to extract random values of a variety of types. - -Minimal complete definition: 'randomR' and 'random'. - --} - - class Uniform a where uniform :: MonadRandom g m => g -> m a @@ -623,6 +623,10 @@ class UniformRange a where uniformR :: MonadRandom g m => (a, a) -> g -> m a +{- | +With a source of random number supply in hand, the 'Random' class allows the +programmer to extract random values of a variety of types. +-} {-# DEPRECATED randomR "In favor of `uniformR`" #-} {-# DEPRECATED randomRIO "In favor of `uniformR`" #-} {-# DEPRECATED randomIO "In favor of `uniformR`" #-} @@ -637,7 +641,7 @@ class Random a where {-# INLINE randomR #-} randomR :: RandomGen g => (a, a) -> g -> (a, g) default randomR :: (RandomGen g, UniformRange a) => (a, a) -> g -> (a, g) - randomR r g = runGenState g (uniformR r PureGen) + randomR r g = runGenState g (uniformR r PureGenI) -- | The same as 'randomR', but using a default range determined by the type: -- From 0b4d331ab08737a3ab97365bfa376d4835d85bbb Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Mon, 16 Mar 2020 15:08:31 +0100 Subject: [PATCH 041/170] Pin splitmix 0.0.4 --- stack.yaml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/stack.yaml b/stack.yaml index 9acf81245..00998f884 100644 --- a/stack.yaml +++ b/stack.yaml @@ -30,16 +30,23 @@ resolver: lts-15.1 # - wai packages: - . + # Dependency packages to be pulled from upstream that are not in the resolver. # These entries can reference officially published versions as well as # forks / in-progress versions pinned to a git hash. For example: -# -# extra-deps: -# - acme-missiles-0.3 -# - git: https://github.com/commercialhaskell/stack.git -# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a -# -# extra-deps: [] +extra-deps: + # We require a splitmix version that supports the "random" flag. When this + # flag is set to false, splitmix does not depend on the random package. We + # require the flag to be set to false to avoid a circular dependency: + # random -> splitmix -> random. + # The oldest splitmix version to support the "random" flag is 0.0.3. LTS + # snapshots prior to lts-14 contain older versions of splitmix. To make sure + # the build goes through, we pin the splitmix version here. + # + # Note that although the resolver defined in this file comes with a splitmix + # >= 0.0.3 by default, this stack.yaml is also used in CI, where the resolver + # is overridden, making this explicit pinning of splitmix necessary. +- splitmix-0.0.4@sha256:fb9bb8b54a2e76c8a021fe5c4c3798047e1f60e168379a1f80693047fe00ad0e,4813 # Override default flag values for local packages and extra-deps flags: From 8dd2c06f52d65b40c390b8e88aea9e120b27ea58 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Mon, 16 Mar 2020 15:09:09 +0100 Subject: [PATCH 042/170] Enable CI on lts-{7,9,11,12} --- .travis.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index ff13213b9..e9ad8f3b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -97,21 +97,21 @@ jobs: # compiler: ": #stack 7.10.3" # addons: {apt: {packages: [libgmp-dev]}} - #- env: BUILD=stack ARGS="--resolver lts-7" - # compiler: ": #stack 8.0.1" - # addons: {apt: {packages: [libgmp-dev]}} + - env: BUILD=stack ARGS="--resolver lts-7" + compiler: ": #stack 8.0.1" + addons: {apt: {packages: [libgmp-dev]}} - #- env: BUILD=stack ARGS="--resolver lts-9" - # compiler: ": #stack 8.0.2" - # addons: {apt: {packages: [libgmp-dev]}} + - env: BUILD=stack ARGS="--resolver lts-9" + compiler: ": #stack 8.0.2" + addons: {apt: {packages: [libgmp-dev]}} - #- env: BUILD=stack ARGS="--resolver lts-11" - # compiler: ": #stack 8.2.2" - # addons: {apt: {packages: [libgmp-dev]}} + - env: BUILD=stack ARGS="--resolver lts-11" + compiler: ": #stack 8.2.2" + addons: {apt: {packages: [libgmp-dev]}} - #- env: BUILD=stack ARGS="--resolver lts-12" - # compiler: ": #stack 8.4.4" - # addons: {apt: {packages: [libgmp-dev]}} + - env: BUILD=stack ARGS="--resolver lts-12" + compiler: ": #stack 8.4.4" + addons: {apt: {packages: [libgmp-dev]}} - env: BUILD=stack ARGS="--resolver lts-14" compiler: ": #stack 8.6.5" From 18949c24bbee5909516c639badc91f25ca86982b Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Mon, 16 Mar 2020 15:20:30 +0100 Subject: [PATCH 043/170] Pin cabal-doctest --- stack.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stack.yaml b/stack.yaml index 00998f884..d5cd45146 100644 --- a/stack.yaml +++ b/stack.yaml @@ -47,6 +47,8 @@ extra-deps: # >= 0.0.3 by default, this stack.yaml is also used in CI, where the resolver # is overridden, making this explicit pinning of splitmix necessary. - splitmix-0.0.4@sha256:fb9bb8b54a2e76c8a021fe5c4c3798047e1f60e168379a1f80693047fe00ad0e,4813 + # Not contained in all snapshots we want to test against +- cabal-doctest-1.0.8@sha256:34dff6369d417df2699af4e15f06bc181d495eca9c51efde173deae2053c197c,1491 # Override default flag values for local packages and extra-deps flags: From 2db676ac74f6eb1c86794663fadbc38e62bb03de Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Mon, 16 Mar 2020 20:34:17 +0300 Subject: [PATCH 044/170] Disable support for ghc < 8.2 and set bounds for primitive to 0.6.4.0 --- .travis.yml | 16 +++++----- random.cabal | 4 +-- stack-old.yaml | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++ stack.yaml | 16 +--------- 4 files changed, 90 insertions(+), 25 deletions(-) create mode 100644 stack-old.yaml diff --git a/.travis.yml b/.travis.yml index e9ad8f3b8..704f6d0e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -97,19 +97,19 @@ jobs: # compiler: ": #stack 7.10.3" # addons: {apt: {packages: [libgmp-dev]}} - - env: BUILD=stack ARGS="--resolver lts-7" - compiler: ": #stack 8.0.1" - addons: {apt: {packages: [libgmp-dev]}} + # - env: BUILD=stack ARGS="--resolver lts-7" + # compiler: ": #stack 8.0.1" + # addons: {apt: {packages: [libgmp-dev]}} - - env: BUILD=stack ARGS="--resolver lts-9" - compiler: ": #stack 8.0.2" - addons: {apt: {packages: [libgmp-dev]}} + # - env: BUILD=stack ARGS="--resolver lts-9" + # compiler: ": #stack 8.0.2" + # addons: {apt: {packages: [libgmp-dev]}} - - env: BUILD=stack ARGS="--resolver lts-11" + - env: BUILD=stack ARGS="--resolver lts-11 --stack-yaml stack-old.yaml" compiler: ": #stack 8.2.2" addons: {apt: {packages: [libgmp-dev]}} - - env: BUILD=stack ARGS="--resolver lts-12" + - env: BUILD=stack ARGS="--resolver lts-12 --stack-yaml stack-old.yaml" compiler: ": #stack 8.4.4" addons: {apt: {packages: [libgmp-dev]}} diff --git a/random.cabal b/random.cabal index 027977740..478152c1f 100644 --- a/random.cabal +++ b/random.cabal @@ -32,9 +32,9 @@ library default-language: Haskell2010 ghc-options: -Wall build-depends: - base >=3 && <5, + base >=4.10 && <5, bytestring -any, - primitive -any, + primitive >= 0.6.4.0 && <8, mtl -any, splitmix -any diff --git a/stack-old.yaml b/stack-old.yaml new file mode 100644 index 000000000..119fc9b74 --- /dev/null +++ b/stack-old.yaml @@ -0,0 +1,79 @@ +# This file was automatically generated by 'stack init' +# +# Some commonly used options have been documented as comments in this file. +# For advanced use and comprehensive documentation of the format, please see: +# https://docs.haskellstack.org/en/stable/yaml_configuration/ + +# Resolver to choose a 'specific' stackage snapshot or a compiler version. +# A snapshot resolver dictates the compiler version and the set of packages +# to be used for project dependencies. For example: +# +# resolver: lts-3.5 +# resolver: nightly-2015-09-21 +# resolver: ghc-7.10.2 +# +# The location of a snapshot can be provided as a file or url. Stack assumes +# a snapshot provided as a file might change, whereas a url resource does not. +# +# resolver: ./custom-snapshot.yaml +# resolver: https://example.com/snapshots/2018-01-01.yaml +resolver: lts-11.22 + +# User packages to be built. +# Various formats can be used as shown in the example below. +# +# packages: +# - some-directory +# - https://example.com/foo/bar/baz-0.0.2.tar.gz +# subdirs: +# - auto-update +# - wai +packages: +- . + +# Dependency packages to be pulled from upstream that are not in the resolver. +# These entries can reference officially published versions as well as +# forks / in-progress versions pinned to a git hash. For example: +extra-deps: + # We require a splitmix version that supports the "random" flag. When this + # flag is set to false, splitmix does not depend on the random package. We + # require the flag to be set to false to avoid a circular dependency: + # random -> splitmix -> random. + # The oldest splitmix version to support the "random" flag is 0.0.3. LTS + # snapshots prior to lts-14 contain older versions of splitmix. To make sure + # the build goes through, we pin the splitmix version here. + # + # Note that although the resolver defined in this file comes with a splitmix + # >= 0.0.3 by default, this stack.yaml is also used in CI, where the resolver + # is overridden, making this explicit pinning of splitmix necessary. +- splitmix-0.0.4@sha256:fb9bb8b54a2e76c8a021fe5c4c3798047e1f60e168379a1f80693047fe00ad0e,4813 + # Not contained in all snapshots we want to test against +- doctest-0.16.2@sha256:2f96e9bbe9aee11b47453c82c24b3dc76cdbb8a2a7c984dfd60b4906d08adf68,6942 +- cabal-doctest-1.0.8@sha256:34dff6369d417df2699af4e15f06bc181d495eca9c51efde173deae2053c197c,1491 +- primitive-0.6.4.0@sha256:5b6a2c3cc70a35aabd4565fcb9bb1dd78fe2814a36e62428a9a1aae8c32441a1,2079 + +# Override default flag values for local packages and extra-deps +flags: + splitmix: + random: false + +# Extra package databases containing global packages +# extra-package-dbs: [] + +# Control whether we use the GHC we find on the path +# system-ghc: true +# +# Require a specific version of stack, using version ranges +# require-stack-version: -any # Default +# require-stack-version: ">=2.2" +# +# Override the architecture used by stack, especially useful on Windows +# arch: i386 +# arch: x86_64 +# +# Extra directories used by stack for building +# extra-include-dirs: [/path/to/dir] +# extra-lib-dirs: [/path/to/dir] +# +# Allow a newer minor version of GHC than the snapshot specifies +# compiler-check: newer-minor diff --git a/stack.yaml b/stack.yaml index d5cd45146..572921a65 100644 --- a/stack.yaml +++ b/stack.yaml @@ -34,21 +34,7 @@ packages: # Dependency packages to be pulled from upstream that are not in the resolver. # These entries can reference officially published versions as well as # forks / in-progress versions pinned to a git hash. For example: -extra-deps: - # We require a splitmix version that supports the "random" flag. When this - # flag is set to false, splitmix does not depend on the random package. We - # require the flag to be set to false to avoid a circular dependency: - # random -> splitmix -> random. - # The oldest splitmix version to support the "random" flag is 0.0.3. LTS - # snapshots prior to lts-14 contain older versions of splitmix. To make sure - # the build goes through, we pin the splitmix version here. - # - # Note that although the resolver defined in this file comes with a splitmix - # >= 0.0.3 by default, this stack.yaml is also used in CI, where the resolver - # is overridden, making this explicit pinning of splitmix necessary. -- splitmix-0.0.4@sha256:fb9bb8b54a2e76c8a021fe5c4c3798047e1f60e168379a1f80693047fe00ad0e,4813 - # Not contained in all snapshots we want to test against -- cabal-doctest-1.0.8@sha256:34dff6369d417df2699af4e15f06bc181d495eca9c51efde173deae2053c197c,1491 +extra-deps: [] # Override default flag values for local packages and extra-deps flags: From dbce450d6e6a5da5a8d5f4475e8570ae350a2117 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Fri, 20 Mar 2020 10:21:00 +0000 Subject: [PATCH 045/170] Add imports --- System/Random.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/System/Random.hs b/System/Random.hs index 701d71ee4..1c2be159d 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -237,6 +237,7 @@ mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 {-# INLINE mutableByteArrayContentsCompat #-} -- $setup +-- >>> import System.IO (IOMode(WriteMode), hPutStr, withBinaryFile) -- >>> :set -XFlexibleContexts -- >>> :set -fno-warn-missing-methods -- >>> :{ From 71d3e87e51e21de0d677588cd975cddb7b3da775 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Tue, 24 Mar 2020 10:30:00 +0100 Subject: [PATCH 046/170] Add MINIMAL annotation to RandomGen --- System/Random.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/System/Random.hs b/System/Random.hs index 1c2be159d..ce6f49c6b 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -262,6 +262,7 @@ mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 {-# DEPRECATED next "Use genWord32[R] or genWord64[R]" #-} {-# DEPRECATED genRange "Use genWord32[R] or genWord64[R]" #-} class RandomGen g where + {-# MINIMAL (next,genRange)|((genWord32|genWord32R),(genWord64|genWord64R)) #-} -- |The 'next' operation returns an 'Int' that is uniformly -- distributed in the range returned by 'genRange' (including both -- end points), and a new generator. Using 'next' is inefficient as From 5825624dd1ac91bab4a64f9051cef4493ac9bcce Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Tue, 24 Mar 2020 18:35:47 +0300 Subject: [PATCH 047/170] Hide PureGenI constructor --- System/Random.hs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 1c2be159d..b6473b928 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -119,7 +119,7 @@ -- let rolls :: [Word32] -- rolls = runGenState_ -- (PCGen 17 29) --- (randomListM PureGenI 10 >>= \xs -> return $ map ((+1) . (`mod` 6)) xs) +-- (\g -> randomListM g 10 >>= \xs -> return $ map ((+1) . (`mod` 6)) xs) -- :} -- -- >>> rolls @@ -438,7 +438,7 @@ genByteString n g = runPureGenST g (uniformByteStringPrim n) -- -- @since 1.2 runPureGenST :: RandomGen g => g -> (forall s . PureGen g -> StateT g (ST s) a) -> (a, g) -runPureGenST g action = runST $ runGenStateT g $ action PureGenI +runPureGenST g action = runST $ runGenStateT g $ action {-# INLINE runPureGenST #-} @@ -460,8 +460,8 @@ instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) m where -- | Generate a random value in a state monad -- -- @since 1.2 -genRandom :: (RandomGen g, Random a, MonadState g m) => m a -genRandom = randomM PureGenI +genRandom :: (RandomGen g, Random a, MonadState g m) => PureGen g -> m a +genRandom = randomM -- | Split current generator and update the state with one part, while returning the other. -- @@ -469,17 +469,17 @@ genRandom = randomM PureGenI splitGen :: (MonadState g m, RandomGen g) => m g splitGen = state split -runGenState :: RandomGen g => g -> State g a -> (a, g) -runGenState = flip runState +runGenState :: RandomGen g => g -> (PureGen g -> State g a) -> (a, g) +runGenState g f = runState (f PureGenI) g -runGenState_ :: RandomGen g => g -> State g a -> a -runGenState_ g = fst . flip runState g +runGenState_ :: RandomGen g => g -> (PureGen g -> State g a) -> a +runGenState_ g = fst . runGenState g -runGenStateT :: RandomGen g => g -> StateT g m a -> m (a, g) -runGenStateT = flip runStateT +runGenStateT :: RandomGen g => g -> (PureGen g -> StateT g m a) -> m (a, g) +runGenStateT g f = runStateT (f PureGenI) g -runGenStateT_ :: (RandomGen g, Functor f) => g -> StateT g f a -> f a -runGenStateT_ g = fmap fst . flip runStateT g +runGenStateT_ :: (RandomGen g, Functor f) => g -> (PureGen g -> StateT g f a) -> f a +runGenStateT_ g = fmap fst . runGenStateT g -- | This is a wrapper wround pure generator that can be used in an effectful environment. -- It is safe in presence of concurrency since all operations are performed atomically. @@ -691,7 +691,7 @@ class Random a where {-# INLINE randomR #-} randomR :: RandomGen g => (a, a) -> g -> (a, g) default randomR :: (RandomGen g, UniformRange a) => (a, a) -> g -> (a, g) - randomR r g = runGenState g (uniformR r PureGenI) + randomR r g = runGenState g (uniformR r) -- | The same as 'randomR', but using a default range determined by the type: -- From 6fab0d0e993322d88d3abef6f40b168f462cb26f Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Tue, 24 Mar 2020 19:48:25 +0300 Subject: [PATCH 048/170] Addition of coveralls to travis. Extra CI related changes: * Update readme with badges * Disable separate step for ghc installation with stack. * Add build with nightly that is allowed to fail --- .travis.yml | 24 ++++++++++++++++++------ README.md | 19 ++++++++++++++++--- stack-coveralls.yaml | 7 +++++++ 3 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 stack-coveralls.yaml diff --git a/.travis.yml b/.travis.yml index 704f6d0e3..15e4f88a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -105,20 +105,24 @@ jobs: # compiler: ": #stack 8.0.2" # addons: {apt: {packages: [libgmp-dev]}} - - env: BUILD=stack ARGS="--resolver lts-11 --stack-yaml stack-old.yaml" + - env: BUILD=stack ARGS="--resolver lts-11.22 --stack-yaml stack-old.yaml" compiler: ": #stack 8.2.2" addons: {apt: {packages: [libgmp-dev]}} - - env: BUILD=stack ARGS="--resolver lts-12 --stack-yaml stack-old.yaml" + - env: BUILD=stack ARGS="--resolver lts-12.26 --stack-yaml stack-old.yaml" compiler: ": #stack 8.4.4" addons: {apt: {packages: [libgmp-dev]}} - - env: BUILD=stack ARGS="--resolver lts-14" + - env: BUILD=stack ARGS="--resolver lts-14.27" COVERALLS_STACK_YAML="stack-coveralls.yaml" compiler: ": #stack 8.6.5" addons: {apt: {packages: [libgmp-dev]}} - env: BUILD=stack ARGS="--resolver lts-15" - compiler: ": #stack 8.8.2" + compiler: ": #stack 8.8.3" + addons: {apt: {packages: [libgmp-dev]}} + + - env: BUILD=stack ARGS="--resolver nightly" + compiler: ": #stack" addons: {apt: {packages: [libgmp-dev]}} # Nightly builds are allowed to fail @@ -215,7 +219,7 @@ install: # stack --no-terminal $ARGS solver --update-config) # Build the dependencies - stack --no-terminal --install-ghc $ARGS test --bench --only-dependencies + # stack --no-terminal --install-ghc $ARGS test --bench --only-dependencies ;; cabal) cabal --version @@ -236,7 +240,15 @@ script: set -ex case "$BUILD" in stack) - stack --no-terminal $ARGS test --bench --no-run-benchmarks --haddock --no-haddock-deps + BUILD_ARGS="--bench --no-run-benchmarks --haddock --no-haddock-deps" + if [ -n "${COVERALLS_STACK_YAML}" ] && [ -n "${COVERALLS_REPO_TOKEN}" ]; then + stack $ARGS --stack-yaml="$COVERALLS_STACK_YAML" test --coverage $BUILD_ARGS + stack $ARGS --stack-yaml="$COVERALLS_STACK_YAML" hpc report --all + travis_retry curl -L https://github.com/lehins/stack-hpc-coveralls/releases/download/0.0.5.0/shc.tar.gz | tar xz shc + STACK_YAML="$COVERALLS_STACK_YAML" ./shc --repo-token=$COVERALLS_REPO_TOKEN combined custom + else + stack --no-terminal $ARGS test $BUILD_ARGS + fi ;; cabal) cabal install --enable-tests --enable-benchmarks --force-reinstalls --ghc-options=-O0 --reorder-goals --max-backjumps=-1 $CABALARGS $PACKAGES diff --git a/README.md b/README.md index 04ab37055..67909d776 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,19 @@ -The Haskell Standard Library -- Random Number Generation -======================================================== -[![Build Status](https://secure.travis-ci.org/idontgetoutmuch/random.svg?branch=master)](http://travis-ci.org/idontgetoutmuch/random) +# The Haskell Standard Library + +## Random Number Generation + +### Status + +| Language | Travis | Coveralls | +|:--------:|:------:|:---------:| +| ![GitHub top language](https://img.shields.io/github/languages/top/idontgetoutmuch/random.svg) | [![Build Status](https://secure.travis-ci.org/idontgetoutmuch/random.svg?branch=master)](http://travis-ci.org/idontgetoutmuch/random) | [![Coverage Status](https://coveralls.io/repos/github/idontgetoutmuch/random/badge.svg?branch=master)](https://coveralls.io/github/idontgetoutmuch/random?branch=master) + +| Package | Hackage | Nightly | LTS | +|:-------------------|:-------:|:-------:|:---:| +| [`random`](https://github.com/idontgetoutmuch/random)| [![Hackage](https://img.shields.io/hackage/v/random.svg)](https://hackage.haskell.org/package/random)| [![Nightly](https://www.stackage.org/package/random/badge/nightly)](https://www.stackage.org/nightly/package/random)| [![Nightly](https://www.stackage.org/package/random/badge/lts)](https://www.stackage.org/lts/package/random) + + +### Description This library provides a basic interface for (splittable) random number generators. diff --git a/stack-coveralls.yaml b/stack-coveralls.yaml new file mode 100644 index 000000000..948886305 --- /dev/null +++ b/stack-coveralls.yaml @@ -0,0 +1,7 @@ +resolver: lts-14.27 +packages: +- . +extra-deps: [] +flags: + splitmix: + random: false From 38d38ab65767cee47ed44b0acb8f67e813771d89 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Tue, 24 Mar 2020 21:21:20 +0300 Subject: [PATCH 049/170] Update badges in README to point to interface-to-performance branch [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 67909d776..c3b493e99 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ | Language | Travis | Coveralls | |:--------:|:------:|:---------:| -| ![GitHub top language](https://img.shields.io/github/languages/top/idontgetoutmuch/random.svg) | [![Build Status](https://secure.travis-ci.org/idontgetoutmuch/random.svg?branch=master)](http://travis-ci.org/idontgetoutmuch/random) | [![Coverage Status](https://coveralls.io/repos/github/idontgetoutmuch/random/badge.svg?branch=master)](https://coveralls.io/github/idontgetoutmuch/random?branch=master) +| ![GitHub top language](https://img.shields.io/github/languages/top/idontgetoutmuch/random.svg) | [![Build Status](https://secure.travis-ci.org/idontgetoutmuch/random.svg?branch=interface-to-performance)](http://travis-ci.org/idontgetoutmuch/random) | [![Coverage Status](https://coveralls.io/repos/github/idontgetoutmuch/random/badge.svg?branch=interface-to-performance)](https://coveralls.io/github/idontgetoutmuch/random?branch=interface-to-performance) | Package | Hackage | Nightly | LTS | |:-------------------|:-------:|:-------:|:---:| From 9b9083ea307b0d0e53f3be031ffa1dd8a75bfcd1 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Mon, 23 Mar 2020 15:25:06 +0000 Subject: [PATCH 050/170] UniformRange for Float and Double --- System/Random.hs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index b6473b928..f4c68f1f8 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -224,6 +224,9 @@ import GHC.ForeignPtr import System.IO.Unsafe (unsafePerformIO) import qualified System.Random.SplitMix as SM +import Data.Char (chr, ord) +import GHC.Float + #if !MIN_VERSION_primitive(0,7,0) import Data.Primitive.Types (Addr(..)) @@ -1002,7 +1005,11 @@ instance Random Double where random = randomDouble randomM = uniformR (0, 1) -instance UniformRange Double +instance UniformRange Double where + uniformR (l, h) g = do + w64 <- uniformWord64 g + let x = castWord64ToDouble $ (w64 `shiftR` 12) .|. 0x3ff0000000000000 + return $ (h - l) * (x - 1.0) + l randomDouble :: RandomGen b => b -> (Double, b) randomDouble rng = @@ -1021,7 +1028,11 @@ instance Random Float where random = randomFloat randomM = uniformR (0, 1) -instance UniformRange Float +instance UniformRange Float where + uniformR (l, h) g = do + w32 <- uniformWord32 g + let x = castWord32ToFloat $ (w32 `shiftR` 9) .|. 0x3f800000 + return $ (h - l) * (x - 1.0) + l randomFloat :: RandomGen b => b -> (Float, b) randomFloat rng = From 39140e658b25645701e0e43d9fd5497db44f59d3 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Wed, 25 Mar 2020 15:17:30 +0000 Subject: [PATCH 051/170] Test conditional compilation --- System/Random.hs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index f4c68f1f8..1e3a1ebae 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -223,8 +223,6 @@ import GHC.Exts (Ptr(..), build) import GHC.ForeignPtr import System.IO.Unsafe (unsafePerformIO) import qualified System.Random.SplitMix as SM - -import Data.Char (chr, ord) import GHC.Float #if !MIN_VERSION_primitive(0,7,0) @@ -1006,10 +1004,12 @@ instance Random Double where randomM = uniformR (0, 1) instance UniformRange Double where +#if __GLASGOW_HASKELL__ >= 844 uniformR (l, h) g = do w64 <- uniformWord64 g let x = castWord64ToDouble $ (w64 `shiftR` 12) .|. 0x3ff0000000000000 return $ (h - l) * (x - 1.0) + l +#endif randomDouble :: RandomGen b => b -> (Double, b) randomDouble rng = @@ -1029,10 +1029,12 @@ instance Random Float where randomM = uniformR (0, 1) instance UniformRange Float where +#if __GLASGOW_HASKELL__ >= 844 uniformR (l, h) g = do w32 <- uniformWord32 g let x = castWord32ToFloat $ (w32 `shiftR` 9) .|. 0x3f800000 return $ (h - l) * (x - 1.0) + l +#endif randomFloat :: RandomGen b => b -> (Float, b) randomFloat rng = From f81725c3d2f402d14eafc3764ad40f9ba71598a4 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Wed, 25 Mar 2020 17:10:05 +0000 Subject: [PATCH 052/170] Support earlier versions of ghc for Double --- System/Random.hs | 12 ++++++++++-- tests/Spec.hs | 7 ++++++- tests/Spec/Bitmask.hs | 5 ++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 1e3a1ebae..70fa3dbff 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1004,11 +1004,15 @@ instance Random Double where randomM = uniformR (0, 1) instance UniformRange Double where -#if __GLASGOW_HASKELL__ >= 844 +#if __GLASGOW_HASKELL__ >= 804 uniformR (l, h) g = do w64 <- uniformWord64 g let x = castWord64ToDouble $ (w64 `shiftR` 12) .|. 0x3ff0000000000000 return $ (h - l) * (x - 1.0) + l +#else + uniformR (l, h) g = do + let x = fst $ randomDouble g + return $ (h - l) * x + l #endif randomDouble :: RandomGen b => b -> (Double, b) @@ -1029,11 +1033,15 @@ instance Random Float where randomM = uniformR (0, 1) instance UniformRange Float where -#if __GLASGOW_HASKELL__ >= 844 +#if __GLASGOW_HASKELL__ >= 804 uniformR (l, h) g = do w32 <- uniformWord32 g let x = castWord32ToFloat $ (w32 `shiftR` 9) .|. 0x3f800000 return $ (h - l) * (x - 1.0) + l +#else + uniformR (l, h) g = do + let x = fst $ randomFloat g + return $ (h - l) * x + l #endif randomFloat :: RandomGen b => b -> (Float, b) diff --git a/tests/Spec.hs b/tests/Spec.hs index 65d85b315..ecb368b6c 100644 --- a/tests/Spec.hs +++ b/tests/Spec.hs @@ -14,7 +14,7 @@ import qualified Spec.Bitmask as Range main :: IO () main = defaultMain $ testGroup "Spec" [ bitmaskSpecWord32, bitmaskSpecWord64 - , rangeSpecWord32, rangeSpecInt + , rangeSpecWord32, rangeSpecDouble, rangeSpecInt ] bitmaskSpecWord32 :: TestTree @@ -38,6 +38,11 @@ rangeSpecWord32 = testGroup "uniformR (Word32)" , SC.testProperty "(Word32) singleton" $ seeded $ Range.singleton @StdGen @Word32 ] +rangeSpecDouble :: TestTree +rangeSpecDouble = testGroup "uniformR (Word32)" + [ SC.testProperty "(Double) uniform bounded" $ seeded $ Range.uniformBounded @StdGen @Double + ] + rangeSpecInt :: TestTree rangeSpecInt = testGroup "uniformR (Int)" [ SC.testProperty "(Int) symmetric" $ seeded $ Range.symmetric @StdGen @Int diff --git a/tests/Spec/Bitmask.hs b/tests/Spec/Bitmask.hs index bd43505a0..835a5c1cc 100644 --- a/tests/Spec/Bitmask.hs +++ b/tests/Spec/Bitmask.hs @@ -1,4 +1,4 @@ -module Spec.Bitmask (symmetric, bounded, singleton) where +module Spec.Bitmask (symmetric, bounded, singleton, uniformBounded) where import Data.Bits import System.Random @@ -17,3 +17,6 @@ singleton :: (RandomGen g, FiniteBits a, Num a, Ord a, Random a) => g -> a -> Bo singleton g x = result == x where result = fst (bitmaskWithRejection (x, x) g) + +uniformBounded :: (RandomGen g, UniformRange a, Ord a) => g -> (a, a) -> Bool +uniformBounded g (l, r) = runGenState_ g (\g -> (uniformR (l, r) g >>= \result -> return ((min l r) <= result))) From 973d3d2163dc35cd7bc0c518b6fa18da93df7459 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Thu, 26 Mar 2020 11:04:29 +0000 Subject: [PATCH 053/170] Fix(!) conditional compilation and address comments --- System/Random.hs | 30 ++++++++++++++++++++++++------ tests/Spec.hs | 9 +++++++-- tests/Spec/Bitmask.hs | 2 +- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 70fa3dbff..262eedd14 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1007,14 +1007,23 @@ instance UniformRange Double where #if __GLASGOW_HASKELL__ >= 804 uniformR (l, h) g = do w64 <- uniformWord64 g - let x = castWord64ToDouble $ (w64 `shiftR` 12) .|. 0x3ff0000000000000 + let x = castWord64ToDouble $ (w64 `unsafeShiftR` 12) .|. 0x3ff0000000000000 return $ (h - l) * (x - 1.0) + l #else uniformR (l, h) g = do - let x = fst $ randomDouble g - return $ (h - l) * x + l + x <- uniformDouble g + return $ (h - l) * x + l #endif +-- A copy of 'randomDouble' required for old versions of GHC +uniformDouble :: MonadRandom g m => g -> m Double +uniformDouble g = do + w64 <- uniformWord64 g + return $ fromIntegral (mask53 .&. w64) / fromIntegral twoto53 + where + twoto53 = (2::Word64) ^ (53::Word64) + mask53 = twoto53 - 1 + randomDouble :: RandomGen b => b -> (Double, b) randomDouble rng = case random rng of @@ -1036,14 +1045,23 @@ instance UniformRange Float where #if __GLASGOW_HASKELL__ >= 804 uniformR (l, h) g = do w32 <- uniformWord32 g - let x = castWord32ToFloat $ (w32 `shiftR` 9) .|. 0x3f800000 + let x = castWord32ToFloat $ (w32 `unsafeShiftR` 9) .|. 0x3f800000 return $ (h - l) * (x - 1.0) + l #else uniformR (l, h) g = do - let x = fst $ randomFloat g - return $ (h - l) * x + l + x <- uniformFloat g + return $ (h - l) * x + l #endif +-- A copy of 'randomFloat' required for old versions of GHC +uniformFloat :: MonadRandom g m => g -> m Float +uniformFloat g = do + w32 <- uniformWord32 g + return $ fromIntegral (mask24 .&. w32) / fromIntegral twoto24 + where + mask24 = twoto24 - 1 + twoto24 = (2::Word32) ^ (24::Word32) + randomFloat :: RandomGen b => b -> (Float, b) randomFloat rng = -- TODO: Faster to just use 'next' IF it generates enough bits of randomness. diff --git a/tests/Spec.hs b/tests/Spec.hs index ecb368b6c..bd33d58e4 100644 --- a/tests/Spec.hs +++ b/tests/Spec.hs @@ -14,7 +14,7 @@ import qualified Spec.Bitmask as Range main :: IO () main = defaultMain $ testGroup "Spec" [ bitmaskSpecWord32, bitmaskSpecWord64 - , rangeSpecWord32, rangeSpecDouble, rangeSpecInt + , rangeSpecWord32, rangeSpecDouble, rangeSpecFloat, rangeSpecInt ] bitmaskSpecWord32 :: TestTree @@ -39,10 +39,15 @@ rangeSpecWord32 = testGroup "uniformR (Word32)" ] rangeSpecDouble :: TestTree -rangeSpecDouble = testGroup "uniformR (Word32)" +rangeSpecDouble = testGroup "uniformR (Double)" [ SC.testProperty "(Double) uniform bounded" $ seeded $ Range.uniformBounded @StdGen @Double ] +rangeSpecFloat :: TestTree +rangeSpecFloat = testGroup "uniformR (Float)" + [ SC.testProperty "(Float) uniform bounded" $ seeded $ Range.uniformBounded @StdGen @Float + ] + rangeSpecInt :: TestTree rangeSpecInt = testGroup "uniformR (Int)" [ SC.testProperty "(Int) symmetric" $ seeded $ Range.symmetric @StdGen @Int diff --git a/tests/Spec/Bitmask.hs b/tests/Spec/Bitmask.hs index 835a5c1cc..1aa814dce 100644 --- a/tests/Spec/Bitmask.hs +++ b/tests/Spec/Bitmask.hs @@ -19,4 +19,4 @@ singleton g x = result == x result = fst (bitmaskWithRejection (x, x) g) uniformBounded :: (RandomGen g, UniformRange a, Ord a) => g -> (a, a) -> Bool -uniformBounded g (l, r) = runGenState_ g (\g -> (uniformR (l, r) g >>= \result -> return ((min l r) <= result))) +uniformBounded g (l, r) = runGenState_ g (\g -> (uniformR (l, r) g >>= \result -> return ((min l r) <= result && result <= (max l r)))) From 0134d0f92cf26279de680ec5b4860b9c82e3f36d Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 26 Mar 2020 13:01:48 +0100 Subject: [PATCH 054/170] Factor version-specific code into own functions --- System/Random.hs | 54 ++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 262eedd14..b675000db 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1004,25 +1004,25 @@ instance Random Double where randomM = uniformR (0, 1) instance UniformRange Double where -#if __GLASGOW_HASKELL__ >= 804 uniformR (l, h) g = do w64 <- uniformWord64 g - let x = castWord64ToDouble $ (w64 `unsafeShiftR` 12) .|. 0x3ff0000000000000 - return $ (h - l) * (x - 1.0) + l -#else - uniformR (l, h) g = do - x <- uniformDouble g + let x = word64ToDoubleInUnitInterval w64 return $ (h - l) * x + l -#endif --- A copy of 'randomDouble' required for old versions of GHC -uniformDouble :: MonadRandom g m => g -> m Double -uniformDouble g = do - w64 <- uniformWord64 g - return $ fromIntegral (mask53 .&. w64) / fromIntegral twoto53 +-- | Turns a given uniformly distributed 'Word64' value into a uniformly +-- distributed 'Double' value. +word64ToDoubleInUnitInterval :: Word64 -> Double +#if __GLASGOW_HASKELL__ >= 804 +word64ToDoubleInUnitInterval w64 = between1and2 - 1.0 + where + between1and2 = castWord64ToDouble $ (w64 `unsafeShiftR` 12) .|. 0x3ff0000000000000 +#else +word64ToDoubleInUnitInterval w64 = fromIntegral (mask53 .&. w64) / fromIntegral twoto53 where twoto53 = (2::Word64) ^ (53::Word64) mask53 = twoto53 - 1 +#endif +{-# INLINE word64ToDoubleInUnitInterval #-} randomDouble :: RandomGen b => b -> (Double, b) randomDouble rng = @@ -1042,25 +1042,25 @@ instance Random Float where randomM = uniformR (0, 1) instance UniformRange Float where -#if __GLASGOW_HASKELL__ >= 804 uniformR (l, h) g = do w32 <- uniformWord32 g - let x = castWord32ToFloat $ (w32 `unsafeShiftR` 9) .|. 0x3f800000 - return $ (h - l) * (x - 1.0) + l -#else - uniformR (l, h) g = do - x <- uniformFloat g + let x = word32ToFloatInUnitInterval w32 return $ (h - l) * x + l -#endif --- A copy of 'randomFloat' required for old versions of GHC -uniformFloat :: MonadRandom g m => g -> m Float -uniformFloat g = do - w32 <- uniformWord32 g - return $ fromIntegral (mask24 .&. w32) / fromIntegral twoto24 - where - mask24 = twoto24 - 1 - twoto24 = (2::Word32) ^ (24::Word32) +-- | Turns a given uniformly distributed 'Word32' value into a uniformly +-- distributed 'Float' value. +word32ToFloatInUnitInterval :: Word32 -> Float +#if __GLASGOW_HASKELL__ >= 804 +word32ToFloatInUnitInterval w32 = between1and2 - 1.0 + where + between1and2 = castWord32ToFloat $ (w32 `unsafeShiftR` 9) .|. 0x3f800000 +#else +word32ToFloatInUnitInterval w32 = fromIntegral (mask24 .&. w32) / fromIntegral twoto24 + where + mask24 = twoto24 - 1 + twoto24 = (2::Word32) ^ (24::Word32) +#endif +{-# INLINE word32ToFloatInUnitInterval #-} randomFloat :: RandomGen b => b -> (Float, b) randomFloat rng = From afccab0b40ac3bb0c9fe4244ec86b542e46fb6f0 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 27 Mar 2020 10:30:32 +0100 Subject: [PATCH 055/170] Use uniform instead of randomM in example --- System/Random.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/System/Random.hs b/System/Random.hs index b6473b928..29af65181 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -534,7 +534,7 @@ runPrimGenST_ g action = fst $ runPrimGenST g action -- | Functions like 'runPrimGenIO' are necessary for example if you -- wish to write a function like -- --- >>> let ioGen gen = withBinaryFile "foo.txt" WriteMode $ \h -> ((randomM gen) :: IO Word32) >>= (hPutStr h . show) +-- >>> let ioGen gen = withBinaryFile "foo.txt" WriteMode $ \h -> ((uniform gen) :: IO Word32) >>= (hPutStr h . show) -- -- and then run it -- From 45d23413c6c3d51ee18613fcfeafa38334264084 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Fri, 27 Mar 2020 10:19:01 +0000 Subject: [PATCH 056/170] Hand roll the technique used in GHC.Float for older GHC --- System/Random.hs | 37 ++++++++++++++++---- cbits/CastFloatWord.cmm | 76 +++++++++++++++++++++++++++++++++++++++++ random.cabal | 19 ++++++----- 3 files changed, 116 insertions(+), 16 deletions(-) create mode 100644 cbits/CastFloatWord.cmm diff --git a/System/Random.hs b/System/Random.hs index b675000db..462187675 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -7,6 +7,8 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE GHCForeignImportPrim #-} +{-# LANGUAGE UnliftedFFITypes #-} #if __GLASGOW_HASKELL__ >= 701 {-# LANGUAGE Trustworthy #-} #endif @@ -219,12 +221,17 @@ import Foreign.C.Types import Foreign.Marshal.Alloc (alloca) import Foreign.Ptr (plusPtr) import Foreign.Storable (peekByteOff, pokeByteOff) -import GHC.Exts (Ptr(..), build) +import GHC.Exts (Ptr(..)) import GHC.ForeignPtr import System.IO.Unsafe (unsafePerformIO) import qualified System.Random.SplitMix as SM import GHC.Float +import Data.Bits +import GHC.Base +import GHC.Word + + #if !MIN_VERSION_primitive(0,7,0) import Data.Primitive.Types (Addr(..)) @@ -1017,13 +1024,30 @@ word64ToDoubleInUnitInterval w64 = between1and2 - 1.0 where between1and2 = castWord64ToDouble $ (w64 `unsafeShiftR` 12) .|. 0x3ff0000000000000 #else -word64ToDoubleInUnitInterval w64 = fromIntegral (mask53 .&. w64) / fromIntegral twoto53 +word64ToDoubleInUnitInterval w64 = between1and2 - 1.0 where - twoto53 = (2::Word64) ^ (53::Word64) - mask53 = twoto53 - 1 + between1and2 = castWord64ToDouble' $ (w64 `unsafeShiftR` 12) .|. 0x3ff0000000000000 #endif {-# INLINE word64ToDoubleInUnitInterval #-} +{-# INLINE castWord32ToFloat' #-} +castWord32ToFloat' :: Word32 -> Float +castWord32ToFloat' (W32# w#) = F# (stgWord32ToFloat' w#) + +foreign import prim "stg_word32ToFloatyg" + stgWord32ToFloat' :: Word# -> Float# + +{-# INLINE castWord64ToDouble' #-} +castWord64ToDouble' :: Word64 -> Double +castWord64ToDouble' (W64# w) = D# (stgWord64ToDouble' w) + +foreign import prim "stg_word64ToDoubleyg" +#if WORD_SIZE_IN_BITS == 64 + stgWord64ToDouble' :: Word# -> Double# +#else + stgWord64ToDouble' :: Word64# -> Double# +#endif + randomDouble :: RandomGen b => b -> (Double, b) randomDouble rng = case random rng of @@ -1055,10 +1079,9 @@ word32ToFloatInUnitInterval w32 = between1and2 - 1.0 where between1and2 = castWord32ToFloat $ (w32 `unsafeShiftR` 9) .|. 0x3f800000 #else -word32ToFloatInUnitInterval w32 = fromIntegral (mask24 .&. w32) / fromIntegral twoto24 +word32ToFloatInUnitInterval w32 = between1and2 - 1.0 where - mask24 = twoto24 - 1 - twoto24 = (2::Word32) ^ (24::Word32) + between1and2 = castWord32ToFloat' $ (w32 `unsafeShiftR` 9) .|. 0x3f800000 #endif {-# INLINE word32ToFloatInUnitInterval #-} diff --git a/cbits/CastFloatWord.cmm b/cbits/CastFloatWord.cmm new file mode 100644 index 000000000..ec85905f5 --- /dev/null +++ b/cbits/CastFloatWord.cmm @@ -0,0 +1,76 @@ +#include "Cmm.h" +#include "MachDeps.h" + +#if WORD_SIZE_IN_BITS == 64 +#define DOUBLE_SIZE_WDS 1 +#else +#define DOUBLE_SIZE_WDS 2 +#endif + +#if SIZEOF_W == 4 +#define TO_ZXW_(x) %zx32(x) +#elif SIZEOF_W == 8 +#define TO_ZXW_(x) %zx64(x) +#endif + +stg_word64ToDoubleyg(I64 w) +{ + D_ d; + P_ ptr; + + STK_CHK_GEN_N (DOUBLE_SIZE_WDS); + + reserve DOUBLE_SIZE_WDS = ptr { + I64[ptr] = w; + d = D_[ptr]; + } + + return (d); +} + +stg_doubleToWord64yg(D_ d) +{ + I64 w; + P_ ptr; + + STK_CHK_GEN_N (DOUBLE_SIZE_WDS); + + reserve DOUBLE_SIZE_WDS = ptr { + D_[ptr] = d; + w = I64[ptr]; + } + + return (w); +} + +stg_word32ToFloatyg(W_ w) +{ + F_ f; + P_ ptr; + + STK_CHK_GEN_N (1); + + reserve 1 = ptr { + I32[ptr] = %lobits32(w); + f = F_[ptr]; + } + + return (f); +} + +stg_floatToWord32yg(F_ f) +{ + W_ w; + P_ ptr; + + STK_CHK_GEN_N (1); + + reserve 1 = ptr { + F_[ptr] = f; + // Fix #16617: use zero-extending (TO_ZXW_) here + w = TO_ZXW_(I32[ptr]); + } + + return (w); +} + diff --git a/random.cabal b/random.cabal index 478152c1f..f4453b733 100644 --- a/random.cabal +++ b/random.cabal @@ -37,6 +37,7 @@ library primitive >= 0.6.4.0 && <8, mtl -any, splitmix -any + c-sources: cbits/CastFloatWord.cmm test-suite legacy type: exitcode-stdio-1.0 @@ -56,15 +57,15 @@ test-suite legacy containers -any, random -any -test-suite doctests - type: exitcode-stdio-1.0 - main-is: doctests.hs - hs-source-dirs: tests - default-language: Haskell2010 - build-depends: - base -any, - doctest >=0.15, - random -any +-- test-suite doctests +-- type: exitcode-stdio-1.0 +-- main-is: doctests.hs +-- hs-source-dirs: tests +-- default-language: Haskell2010 +-- build-depends: +-- base -any, +-- doctest >=0.15, +-- random -any test-suite spec type: exitcode-stdio-1.0 From 1d5b00454c1a61550e36c524a2318b842e6a2027 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 27 Mar 2020 13:07:07 +0100 Subject: [PATCH 057/170] Fix doctests --- System/Random.hs | 49 +++++++++++++++-------------------------------- random.cabal | 18 ++++++++--------- tests/doctests.hs | 4 +++- 3 files changed, 27 insertions(+), 44 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 462187675..341f8a099 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -84,27 +84,20 @@ -- (Word16, Word16)@ is a function to pull apart a 'Word32' into a -- pair of 'Word16'): -- --- >>> data PCGen' = PCGen' !Word64 !Word64 +-- >>> newtype PCGen' = PCGen' { unPCGen :: PCGen } +-- +-- >>> let stepGen' = second PCGen' . stepGen . unPCGen -- -- >>> :{ -- instance RandomGen PCGen' where --- genWord8 (PCGen' s i) = (z, PCGen' s' i') --- where --- (x, PCGen s' i') = stepGen (PCGen s i) --- y = fst $ unBuildWord32 x --- z = fst $ unBuildWord16 y --- genWord16 (PCGen' s i) = (y, PCGen' s' i') --- where --- (x, PCGen s' i') = stepGen (PCGen s i) --- y = fst $ unBuildWord32 x --- genWord32 (PCGen' s i) = (x, PCGen' s' i') --- where --- (x, PCGen s' i') = stepGen (PCGen s i) --- genWord64 (PCGen' s i) = (undefined, PCGen' s i) --- where --- (x, g) = stepGen (PCGen s i) --- (y, PCGen s' i') = stepGen g --- split _ = error "This PRNG is not splittable" +-- genWord8 = first fromIntegral . stepGen' +-- genWord16 = first fromIntegral . stepGen' +-- genWord32 = stepGen' +-- genWord64 g = (buildWord64 x y, g'') +-- where +-- (x, g') = stepGen' g +-- (y, g'') = stepGen' g' +-- buildWord64 w0 w1 = ((fromIntegral w1) `shiftL` 32) .|. (fromIntegral w0) -- :} -- -- [/Example for RNG Users:/] @@ -245,25 +238,13 @@ mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 {-# INLINE mutableByteArrayContentsCompat #-} -- $setup +-- >>> import Control.Arrow (first, second) +-- >>> import Control.Monad (replicateM) +-- >>> import Data.Bits +-- >>> import Data.Word -- >>> import System.IO (IOMode(WriteMode), hPutStr, withBinaryFile) -- >>> :set -XFlexibleContexts -- >>> :set -fno-warn-missing-methods --- >>> :{ --- unBuildWord32 :: Word32 -> (Word16, Word16) --- unBuildWord32 w = (fromIntegral (shiftR w 16), --- fromIntegral (fromIntegral (maxBound :: Word16) .&. w)) --- :} --- --- >>> :{ --- unBuildWord16 :: Word16 -> (Word8, Word8) --- unBuildWord16 w = (fromIntegral (shiftR w 8), --- fromIntegral (fromIntegral (maxBound :: Word8) .&. w)) --- :} --- --- >>> :{ --- buildWord64 :: Word32 -> Word32 -> Word64 --- buildWord64 w0 w1 = ((fromIntegral w1) `shiftL` 32) .|. (fromIntegral w0) --- :} -- | The class 'RandomGen' provides a common interface to random number -- generators. diff --git a/random.cabal b/random.cabal index f4453b733..a65f6296a 100644 --- a/random.cabal +++ b/random.cabal @@ -57,15 +57,15 @@ test-suite legacy containers -any, random -any --- test-suite doctests --- type: exitcode-stdio-1.0 --- main-is: doctests.hs --- hs-source-dirs: tests --- default-language: Haskell2010 --- build-depends: --- base -any, --- doctest >=0.15, --- random -any +test-suite doctests + type: exitcode-stdio-1.0 + main-is: doctests.hs + hs-source-dirs: tests + default-language: Haskell2010 + build-depends: + base -any, + doctest >=0.15, + random -any test-suite spec type: exitcode-stdio-1.0 diff --git a/tests/doctests.hs b/tests/doctests.hs index bbf7787f8..5fcfbe447 100644 --- a/tests/doctests.hs +++ b/tests/doctests.hs @@ -9,4 +9,6 @@ main = do traverse_ putStrLn args doctest args where - args = flags ++ pkgs ++ module_sources + -- '-fobject-code' is required to get the doctests to build without + -- tripping over the Cmm bits. + args = ["-fobject-code"] ++ flags ++ pkgs ++ module_sources From 7e33718cceed92dce31e864b73a2bc70fb0af675 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Fri, 27 Mar 2020 12:54:59 +0000 Subject: [PATCH 058/170] Consistent floating point for all versions of ghc --- System/Random.hs | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 341f8a099..9ecdf5a4d 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -218,8 +218,6 @@ import GHC.Exts (Ptr(..)) import GHC.ForeignPtr import System.IO.Unsafe (unsafePerformIO) import qualified System.Random.SplitMix as SM -import GHC.Float - import Data.Bits import GHC.Base import GHC.Word @@ -1000,33 +998,29 @@ instance UniformRange Double where -- | Turns a given uniformly distributed 'Word64' value into a uniformly -- distributed 'Double' value. word64ToDoubleInUnitInterval :: Word64 -> Double -#if __GLASGOW_HASKELL__ >= 804 word64ToDoubleInUnitInterval w64 = between1and2 - 1.0 where between1and2 = castWord64ToDouble $ (w64 `unsafeShiftR` 12) .|. 0x3ff0000000000000 -#else -word64ToDoubleInUnitInterval w64 = between1and2 - 1.0 - where - between1and2 = castWord64ToDouble' $ (w64 `unsafeShiftR` 12) .|. 0x3ff0000000000000 -#endif {-# INLINE word64ToDoubleInUnitInterval #-} -{-# INLINE castWord32ToFloat' #-} -castWord32ToFloat' :: Word32 -> Float -castWord32ToFloat' (W32# w#) = F# (stgWord32ToFloat' w#) +-- | These are now in 'GHC.Float' but unpatched in some versions so +-- for now we roll our own. +{-# INLINE castWord32ToFloat #-} +castWord32ToFloat :: Word32 -> Float +castWord32ToFloat (W32# w#) = F# (stgWord32ToFloat w#) foreign import prim "stg_word32ToFloatyg" - stgWord32ToFloat' :: Word# -> Float# + stgWord32ToFloat :: Word# -> Float# -{-# INLINE castWord64ToDouble' #-} -castWord64ToDouble' :: Word64 -> Double -castWord64ToDouble' (W64# w) = D# (stgWord64ToDouble' w) +{-# INLINE castWord64ToDouble #-} +castWord64ToDouble :: Word64 -> Double +castWord64ToDouble (W64# w) = D# (stgWord64ToDouble w) foreign import prim "stg_word64ToDoubleyg" #if WORD_SIZE_IN_BITS == 64 - stgWord64ToDouble' :: Word# -> Double# + stgWord64ToDouble :: Word# -> Double# #else - stgWord64ToDouble' :: Word64# -> Double# + stgWord64ToDouble :: Word64# -> Double# #endif randomDouble :: RandomGen b => b -> (Double, b) @@ -1055,15 +1049,9 @@ instance UniformRange Float where -- | Turns a given uniformly distributed 'Word32' value into a uniformly -- distributed 'Float' value. word32ToFloatInUnitInterval :: Word32 -> Float -#if __GLASGOW_HASKELL__ >= 804 word32ToFloatInUnitInterval w32 = between1and2 - 1.0 where between1and2 = castWord32ToFloat $ (w32 `unsafeShiftR` 9) .|. 0x3f800000 -#else -word32ToFloatInUnitInterval w32 = between1and2 - 1.0 - where - between1and2 = castWord32ToFloat' $ (w32 `unsafeShiftR` 9) .|. 0x3f800000 -#endif {-# INLINE word32ToFloatInUnitInterval #-} randomFloat :: RandomGen b => b -> (Float, b) From b6b480eb9725fd7b176778fdadbfffa55a793141 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Fri, 27 Mar 2020 13:58:21 +0000 Subject: [PATCH 059/170] Minor housekeeping --- System/Random.hs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 189453b80..f2beadc8b 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -218,7 +218,6 @@ import GHC.Exts (Ptr(..)) import GHC.ForeignPtr import System.IO.Unsafe (unsafePerformIO) import qualified System.Random.SplitMix as SM -import Data.Bits import GHC.Base import GHC.Word @@ -996,7 +995,7 @@ instance UniformRange Double where return $ (h - l) * x + l -- | Turns a given uniformly distributed 'Word64' value into a uniformly --- distributed 'Double' value. +-- distributed 'Double' value in the range [0, 1). word64ToDoubleInUnitInterval :: Word64 -> Double word64ToDoubleInUnitInterval w64 = between1and2 - 1.0 where @@ -1047,7 +1046,7 @@ instance UniformRange Float where return $ (h - l) * x + l -- | Turns a given uniformly distributed 'Word32' value into a uniformly --- distributed 'Float' value. +-- distributed 'Float' value in the range [0,1). word32ToFloatInUnitInterval :: Word32 -> Float word32ToFloatInUnitInterval w32 = between1and2 - 1.0 where From 0988e024f2456eb3cc35ca8eeb1a476d17582a9f Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 27 Mar 2020 16:43:01 +0100 Subject: [PATCH 060/170] Compare result of pure and prim runs --- random.cabal | 2 ++ tests/Spec.hs | 7 +++++++ tests/Spec/Run.hs | 12 ++++++++++++ 3 files changed, 21 insertions(+) create mode 100644 tests/Spec/Run.hs diff --git a/random.cabal b/random.cabal index 478152c1f..65a5bf8f4 100644 --- a/random.cabal +++ b/random.cabal @@ -73,11 +73,13 @@ test-suite spec other-modules: Spec.Bitmask Spec.Range + Spec.Run ghc-options: -Wall build-depends: base -any, random -any, + smallcheck -any, tasty -any, tasty-smallcheck -any, tasty-expected-failure -any diff --git a/tests/Spec.hs b/tests/Spec.hs index 65d85b315..f2a1668e7 100644 --- a/tests/Spec.hs +++ b/tests/Spec.hs @@ -4,17 +4,20 @@ module Main (main) where import Data.Word (Word32, Word64) import System.Random +import Test.SmallCheck (monadic) import Test.Tasty import Test.Tasty.ExpectedFailure (expectFail) import Test.Tasty.SmallCheck as SC import qualified Spec.Bitmask as Bitmask import qualified Spec.Bitmask as Range +import qualified Spec.Run as Run main :: IO () main = defaultMain $ testGroup "Spec" [ bitmaskSpecWord32, bitmaskSpecWord64 , rangeSpecWord32, rangeSpecInt + , runSpec ] bitmaskSpecWord32 :: TestTree @@ -45,6 +48,10 @@ rangeSpecInt = testGroup "uniformR (Int)" , SC.testProperty "(Int) singleton" $ seeded $ Range.singleton @StdGen @Int ] +runSpec :: TestTree +runSpec = testGroup "runGenState_ and runPrimGenIO_" + [ SC.testProperty "equal outputs" $ seeded $ \g -> monadic $ Run.runsEqual g ] + -- | Create a StdGen instance from an Int and pass it to the given function. seeded :: (StdGen -> a) -> Int -> a seeded f = f . mkStdGen diff --git a/tests/Spec/Run.hs b/tests/Spec/Run.hs new file mode 100644 index 000000000..dfe6411f7 --- /dev/null +++ b/tests/Spec/Run.hs @@ -0,0 +1,12 @@ +{-# LANGUAGE ScopedTypeVariables #-} + +module Spec.Run (runsEqual) where + +import Data.Word (Word64) +import System.Random + +runsEqual :: RandomGen g => g -> IO Bool +runsEqual g = do + let (pureResult :: Word64) = runGenState_ g uniform + (genResult :: Word64) <- runPrimGenIO_ g uniform + return $ pureResult == genResult From 151fcb6cdaaa589e4a9a5f5994bd036ec32adcde Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Fri, 27 Mar 2020 16:32:34 +0000 Subject: [PATCH 061/170] Respond to feedback --- System/Random.hs | 4 +++- cbits/CastFloatWord.cmm | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/System/Random.hs b/System/Random.hs index f2beadc8b..0534adf32 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1003,7 +1003,9 @@ word64ToDoubleInUnitInterval w64 = between1and2 - 1.0 {-# INLINE word64ToDoubleInUnitInterval #-} -- | These are now in 'GHC.Float' but unpatched in some versions so --- for now we roll our own. +-- for now we roll our own. See +-- https://gitlab.haskell.org/ghc/ghc/-/blob/master/libraries/base/GHC/Float.hs +-- (4bada77d58). {-# INLINE castWord32ToFloat #-} castWord32ToFloat :: Word32 -> Float castWord32ToFloat (W32# w#) = F# (stgWord32ToFloat w#) diff --git a/cbits/CastFloatWord.cmm b/cbits/CastFloatWord.cmm index ec85905f5..77c060606 100644 --- a/cbits/CastFloatWord.cmm +++ b/cbits/CastFloatWord.cmm @@ -1,3 +1,4 @@ +/* This comes as part of your Haskell installation */ #include "Cmm.h" #include "MachDeps.h" From f8f41bead70b0ea59a43be80ebf7d66c0fc366fc Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Fri, 27 Mar 2020 16:57:36 +0000 Subject: [PATCH 062/170] Remove unneeded code and link to source --- cbits/CastFloatWord.cmm | 33 +-------------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/cbits/CastFloatWord.cmm b/cbits/CastFloatWord.cmm index 77c060606..2494960e1 100644 --- a/cbits/CastFloatWord.cmm +++ b/cbits/CastFloatWord.cmm @@ -1,4 +1,4 @@ -/* This comes as part of your Haskell installation */ +/* From: https://gitlab.haskell.org/ghc/ghc/-/blob/6d172e63f3dd3590b0a57371efb8f924f1fcdf05/libraries/base/cbits/CastFloatWord.cmm */ #include "Cmm.h" #include "MachDeps.h" @@ -29,21 +29,6 @@ stg_word64ToDoubleyg(I64 w) return (d); } -stg_doubleToWord64yg(D_ d) -{ - I64 w; - P_ ptr; - - STK_CHK_GEN_N (DOUBLE_SIZE_WDS); - - reserve DOUBLE_SIZE_WDS = ptr { - D_[ptr] = d; - w = I64[ptr]; - } - - return (w); -} - stg_word32ToFloatyg(W_ w) { F_ f; @@ -59,19 +44,3 @@ stg_word32ToFloatyg(W_ w) return (f); } -stg_floatToWord32yg(F_ f) -{ - W_ w; - P_ ptr; - - STK_CHK_GEN_N (1); - - reserve 1 = ptr { - F_[ptr] = f; - // Fix #16617: use zero-extending (TO_ZXW_) here - w = TO_ZXW_(I32[ptr]); - } - - return (w); -} - From 6d914b92106bf307ebf04b22501c2eabe4a1437b Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Fri, 27 Mar 2020 16:59:32 +0000 Subject: [PATCH 063/170] A better link --- System/Random.hs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 0534adf32..81b2bd706 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1004,8 +1004,7 @@ word64ToDoubleInUnitInterval w64 = between1and2 - 1.0 -- | These are now in 'GHC.Float' but unpatched in some versions so -- for now we roll our own. See --- https://gitlab.haskell.org/ghc/ghc/-/blob/master/libraries/base/GHC/Float.hs --- (4bada77d58). +-- https://gitlab.haskell.org/ghc/ghc/-/blob/6d172e63f3dd3590b0a57371efb8f924f1fcdf05/libraries/base/GHC/Float.hs {-# INLINE castWord32ToFloat #-} castWord32ToFloat :: Word32 -> Float castWord32ToFloat (W32# w#) = F# (stgWord32ToFloat w#) From 64048d20f80c65018118a9143fd11c5c58a5b75b Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 29 Mar 2020 19:54:52 +0300 Subject: [PATCH 064/170] Add .ghci file in order to make it possible to use random with ghci --- .ghci | 1 + 1 file changed, 1 insertion(+) create mode 100755 .ghci diff --git a/.ghci b/.ghci new file mode 100755 index 000000000..d42a8637a --- /dev/null +++ b/.ghci @@ -0,0 +1 @@ +:set -fobject-code From 4b43edeba8b23c350c55774c58f6d33a1e0cd5d1 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 29 Mar 2020 21:43:12 +0300 Subject: [PATCH 065/170] Export Frozen state constructors --- System/Random.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/System/Random.hs b/System/Random.hs index e2f8092ae..59dc82d93 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -134,6 +134,7 @@ module System.Random RandomGen(..) , MonadRandom(..) + , Frozen(..) , withGenM -- ** Standard random number generators , StdGen From ea945b02e19f60b825937646bbef9f3d2f32db02 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Sun, 29 Mar 2020 21:54:12 +0300 Subject: [PATCH 066/170] Swap PrimGen and MutGen naming --- System/Random.hs | 174 +++++++++++++++++++++++----------------------- tests/Spec/Run.hs | 2 +- 2 files changed, 88 insertions(+), 88 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 59dc82d93..29e16aa51 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -151,22 +151,22 @@ module System.Random , runGenStateT_ , runPureGenST -- ** Based on PrimMonad - -- *** PrimGen - boxed thread safe state - , PrimGen - , runPrimGenST - , runPrimGenST_ - , runPrimGenIO - , runPrimGenIO_ - , splitPrimGen - , atomicPrimGen - -- *** MutGen - unboxed mutable state + -- *** MutGen - boxed thread safe state , MutGen , runMutGenST , runMutGenST_ , runMutGenIO , runMutGenIO_ , splitMutGen - , applyMutGen + , atomicMutGen + -- *** PrimGen - unboxed mutable state + , PrimGen + , runPrimGenST + , runPrimGenST_ + , runPrimGenIO + , runPrimGenIO_ + , splitPrimGen + , applyPrimGen -- ** The global random number generator @@ -473,130 +473,130 @@ runGenStateT_ g = fmap fst . runGenStateT g -- It is safe in presence of concurrency since all operations are performed atomically. -- -- @since 1.2 -newtype PrimGen s g = PrimGenI (MutVar s g) +newtype MutGen s g = MutGenI (MutVar s g) instance (s ~ PrimState m, PrimMonad m, RandomGen g) => - MonadRandom (PrimGen s g) m where - newtype Frozen (PrimGen s g) = PrimGen g - thawGen (PrimGen g) = fmap PrimGenI (newMutVar g) - freezeGen (PrimGenI gVar) = fmap PrimGen (readMutVar gVar) - uniformWord32R r = atomicPrimGen (genWord32R r) - uniformWord64R r = atomicPrimGen (genWord64R r) - uniformWord8 = atomicPrimGen genWord8 - uniformWord16 = atomicPrimGen genWord16 - uniformWord32 = atomicPrimGen genWord32 - uniformWord64 = atomicPrimGen genWord64 + MonadRandom (MutGen s g) m where + newtype Frozen (MutGen s g) = MutGen g + thawGen (MutGen g) = fmap MutGenI (newMutVar g) + freezeGen (MutGenI gVar) = fmap MutGen (readMutVar gVar) + uniformWord32R r = atomicMutGen (genWord32R r) + uniformWord64R r = atomicMutGen (genWord64R r) + uniformWord8 = atomicMutGen genWord8 + uniformWord16 = atomicMutGen genWord16 + uniformWord32 = atomicMutGen genWord32 + uniformWord64 = atomicMutGen genWord64 {-# INLINE uniformWord64 #-} - uniformByteArray n = atomicPrimGen (genByteArray n) + uniformByteArray n = atomicMutGen (genByteArray n) -- | Apply a pure operation to generator atomically. -atomicPrimGen :: PrimMonad m => (g -> (a, g)) -> PrimGen (PrimState m) g -> m a -atomicPrimGen op (PrimGenI gVar) = +atomicMutGen :: PrimMonad m => (g -> (a, g)) -> MutGen (PrimState m) g -> m a +atomicMutGen op (MutGenI gVar) = atomicModifyMutVar' gVar $ \g -> case op g of (a, g') -> (g', a) -{-# INLINE atomicPrimGen #-} +{-# INLINE atomicMutGen #-} --- | Split `PrimGen` into atomically updated current generator and a newly created that is +-- | Split `MutGen` into atomically updated current generator and a newly created that is -- returned. -- -- @since 1.2 -splitPrimGen :: +splitMutGen :: (RandomGen g, PrimMonad m) - => PrimGen (PrimState m) g - -> m (PrimGen (PrimState m) g) -splitPrimGen = atomicPrimGen split >=> thawGen . PrimGen + => MutGen (PrimState m) g + -> m (MutGen (PrimState m) g) +splitMutGen = atomicMutGen split >=> thawGen . MutGen -runPrimGenST :: RandomGen g => g -> (forall s . PrimGen s g -> ST s a) -> (a, g) -runPrimGenST g action = runST $ do - primGen <- thawGen $ PrimGen g - res <- action primGen - PrimGen g' <- freezeGen primGen +runMutGenST :: RandomGen g => g -> (forall s . MutGen s g -> ST s a) -> (a, g) +runMutGenST g action = runST $ do + mutGen <- thawGen $ MutGen g + res <- action mutGen + MutGen g' <- freezeGen mutGen pure (res, g') --- | Same as `runPrimGenST`, but discard the resulting generator. -runPrimGenST_ :: RandomGen g => g -> (forall s . PrimGen s g -> ST s a) -> a -runPrimGenST_ g action = fst $ runPrimGenST g action +-- | Same as `runMutGenST`, but discard the resulting generator. +runMutGenST_ :: RandomGen g => g -> (forall s . MutGen s g -> ST s a) -> a +runMutGenST_ g action = fst $ runMutGenST g action --- | Functions like 'runPrimGenIO' are necessary for example if you +-- | Functions like 'runMutGenIO' are necessary for example if you -- wish to write a function like -- -- >>> let ioGen gen = withBinaryFile "foo.txt" WriteMode $ \h -> ((uniform gen) :: IO Word32) >>= (hPutStr h . show) -- -- and then run it -- --- >>> runPrimGenIO_ (mkStdGen 1729) ioGen +-- >>> runMutGenIO_ (mkStdGen 1729) ioGen -- -runPrimGenIO :: (RandomGen g, MonadIO m) => g -> (PrimGen RealWorld g -> m a) -> m (a, g) -runPrimGenIO g action = do - primGen <- liftIO $ thawGen $ PrimGen g - res <- action primGen - PrimGen g' <- liftIO $ freezeGen primGen +runMutGenIO :: (RandomGen g, MonadIO m) => g -> (MutGen RealWorld g -> m a) -> m (a, g) +runMutGenIO g action = do + mutGen <- liftIO $ thawGen $ MutGen g + res <- action mutGen + MutGen g' <- liftIO $ freezeGen mutGen pure (res, g') -{-# INLINE runPrimGenIO #-} +{-# INLINE runMutGenIO #-} --- | Same as `runPrimGenIO`, but discard the resulting generator. -runPrimGenIO_ :: (RandomGen g, MonadIO m) => g -> (PrimGen RealWorld g -> m a) -> m a -runPrimGenIO_ g action = fst <$> runPrimGenIO g action -{-# INLINE runPrimGenIO_ #-} +-- | Same as `runMutGenIO`, but discard the resulting generator. +runMutGenIO_ :: (RandomGen g, MonadIO m) => g -> (MutGen RealWorld g -> m a) -> m a +runMutGenIO_ g action = fst <$> runMutGenIO g action +{-# INLINE runMutGenIO_ #-} -newtype MutGen s g = MutGenI (MutableByteArray s) +newtype PrimGen s g = PrimGenI (MutableByteArray s) instance (s ~ PrimState m, PrimMonad m, RandomGen g, Prim g) => - MonadRandom (MutGen s g) m where - newtype Frozen (MutGen s g) = MutGen g - thawGen (MutGen g) = do + MonadRandom (PrimGen s g) m where + newtype Frozen (PrimGen s g) = PrimGen g + thawGen (PrimGen g) = do ma <- newByteArray (Primitive.sizeOf g) writeByteArray ma 0 g - pure $ MutGenI ma - freezeGen (MutGenI ma) = MutGen <$> readByteArray ma 0 - uniformWord32R r = applyMutGen (genWord32R r) - uniformWord64R r = applyMutGen (genWord64R r) - uniformWord8 = applyMutGen genWord8 - uniformWord16 = applyMutGen genWord16 - uniformWord32 = applyMutGen genWord32 - uniformWord64 = applyMutGen genWord64 - uniformByteArray n = applyMutGen (genByteArray n) - -applyMutGen :: (Prim g, PrimMonad m) => (g -> (a, g)) -> MutGen (PrimState m) g -> m a -applyMutGen f (MutGenI ma) = do + pure $ PrimGenI ma + freezeGen (PrimGenI ma) = PrimGen <$> readByteArray ma 0 + uniformWord32R r = applyPrimGen (genWord32R r) + uniformWord64R r = applyPrimGen (genWord64R r) + uniformWord8 = applyPrimGen genWord8 + uniformWord16 = applyPrimGen genWord16 + uniformWord32 = applyPrimGen genWord32 + uniformWord64 = applyPrimGen genWord64 + uniformByteArray n = applyPrimGen (genByteArray n) + +applyPrimGen :: (Prim g, PrimMonad m) => (g -> (a, g)) -> PrimGen (PrimState m) g -> m a +applyPrimGen f (PrimGenI ma) = do g <- readByteArray ma 0 case f g of (res, g') -> res <$ writeByteArray ma 0 g' --- | Split `MutGen` into atomically updated current generator and a newly created that is +-- | Split `PrimGen` into atomically updated current generator and a newly created that is -- returned. -- -- @since 1.2 -splitMutGen :: +splitPrimGen :: (Prim g, RandomGen g, PrimMonad m) - => MutGen (PrimState m) g - -> m (MutGen (PrimState m) g) -splitMutGen = applyMutGen split >=> thawGen . MutGen + => PrimGen (PrimState m) g + -> m (PrimGen (PrimState m) g) +splitPrimGen = applyPrimGen split >=> thawGen . PrimGen -runMutGenST :: (Prim g, RandomGen g) => g -> (forall s . MutGen s g -> ST s a) -> (a, g) -runMutGenST g action = runST $ do - mutGen <- thawGen $ MutGen g - res <- action mutGen - MutGen g' <- freezeGen mutGen +runPrimGenST :: (Prim g, RandomGen g) => g -> (forall s . PrimGen s g -> ST s a) -> (a, g) +runPrimGenST g action = runST $ do + primGen <- thawGen $ PrimGen g + res <- action primGen + PrimGen g' <- freezeGen primGen pure (res, g') --- | Same as `runMutGenST`, but discard the resulting generator. -runMutGenST_ :: (Prim g, RandomGen g) => g -> (forall s . MutGen s g -> ST s a) -> a -runMutGenST_ g action = fst $ runMutGenST g action +-- | Same as `runPrimGenST`, but discard the resulting generator. +runPrimGenST_ :: (Prim g, RandomGen g) => g -> (forall s . PrimGen s g -> ST s a) -> a +runPrimGenST_ g action = fst $ runPrimGenST g action -runMutGenIO :: (Prim g, RandomGen g, MonadIO m) => g -> (MutGen RealWorld g -> m a) -> m (a, g) -runMutGenIO g action = do - mutGen <- liftIO $ thawGen $ MutGen g - res <- action mutGen - MutGen g' <- liftIO $ freezeGen mutGen +runPrimGenIO :: (Prim g, RandomGen g, MonadIO m) => g -> (PrimGen RealWorld g -> m a) -> m (a, g) +runPrimGenIO g action = do + primGen <- liftIO $ thawGen $ PrimGen g + res <- action primGen + PrimGen g' <- liftIO $ freezeGen primGen pure (res, g') --- | Same as `runMutGenIO`, but discard the resulting generator. -runMutGenIO_ :: (Prim g, RandomGen g, MonadIO m) => g -> (MutGen RealWorld g -> m a) -> m a -runMutGenIO_ g action = fst <$> runMutGenIO g action +-- | Same as `runPrimGenIO`, but discard the resulting generator. +runPrimGenIO_ :: (Prim g, RandomGen g, MonadIO m) => g -> (PrimGen RealWorld g -> m a) -> m a +runPrimGenIO_ g action = fst <$> runPrimGenIO g action type StdGen = SM.SMGen diff --git a/tests/Spec/Run.hs b/tests/Spec/Run.hs index dfe6411f7..e18a38398 100644 --- a/tests/Spec/Run.hs +++ b/tests/Spec/Run.hs @@ -8,5 +8,5 @@ import System.Random runsEqual :: RandomGen g => g -> IO Bool runsEqual g = do let (pureResult :: Word64) = runGenState_ g uniform - (genResult :: Word64) <- runPrimGenIO_ g uniform + (genResult :: Word64) <- runMutGenIO_ g uniform return $ pureResult == genResult From 3676a1777da968555f54d592f7a4a553c0cdc302 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Mon, 30 Mar 2020 01:05:03 +0300 Subject: [PATCH 067/170] Simplify default implementation of both RangomGen and MonadRandom. Improve documentation --- System/Random.hs | 120 +++++++++++++++++------------------------------ 1 file changed, 44 insertions(+), 76 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 29e16aa51..862ae3841 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -48,10 +48,8 @@ -- -- [/Example for RNG Implementors:/] -- --- Suppose you want to use a [permuted congruential --- generator](https://en.wikipedia.org/wiki/Permuted_congruential_generator) --- as the source of entropy (FIXME: is that the correct --- terminology). You can make it an instance of `RandomGen`: +-- Suppose you want to implement a [permuted congruential +-- generator](https://en.wikipedia.org/wiki/Permuted_congruential_generator). -- -- >>> data PCGen = PCGen !Word64 !Word64 -- @@ -68,57 +66,28 @@ -- >>> fst $ stepGen $ snd $ stepGen (PCGen 17 29) -- 3288430965 -- +-- Once implemented it can made an instance of `RandomGen`: +-- -- >>> :{ -- instance RandomGen PCGen where --- next g = (fromIntegral y, h) --- where --- (y, h) = stepGen g +-- genWord32 = stepGen -- split _ = error "This PRNG is not splittable" -- :} -- --- Importantly, this implementation will not be as efficient as it --- could be because the random values are converted to 'Integer' and --- then to desired type. --- --- Instead we should define (where e.g. @unBuildWord32 :: Word32 -> --- (Word16, Word16)@ is a function to pull apart a 'Word32' into a --- pair of 'Word16'): --- --- >>> newtype PCGen' = PCGen' { unPCGen :: PCGen } --- --- >>> let stepGen' = second PCGen' . stepGen . unPCGen --- --- >>> :{ --- instance RandomGen PCGen' where --- genWord8 = first fromIntegral . stepGen' --- genWord16 = first fromIntegral . stepGen' --- genWord32 = stepGen' --- genWord64 g = (buildWord64 x y, g'') --- where --- (x, g') = stepGen' g --- (y, g'') = stepGen' g' --- buildWord64 w0 w1 = ((fromIntegral w1) `shiftL` 32) .|. (fromIntegral w0) --- :} --- -- [/Example for RNG Users:/] -- --- Suppose you want to simulate rolls from a dice (yes I know it's a +-- Suppose you want to simulate a number of rolls from a dice (yes I know it's a -- plural form but it's now common to use it as a singular form): -- -- >>> :{ --- let randomListM :: (MonadRandom g m, Num a, Uniform a) => g -> Int -> m [a] --- randomListM gen n = replicateM n (uniform gen) --- :} --- --- >>> :{ --- let rolls :: [Word32] --- rolls = runGenState_ +-- let rolls :: Int -> [Word] +-- rolls n = runGenState_ -- (PCGen 17 29) --- (\g -> randomListM g 10 >>= \xs -> return $ map ((+1) . (`mod` 6)) xs) +-- (\g -> replicateM n (uniformR (1, 6) g)) -- :} -- --- >>> rolls --- [1,4,2,4,2,2,3,1,5,1] +-- >>> rolls 20 +-- [1,1,5,3,3,3,2,4,3,2,3,3,4,5,1,1,5,1,2,4] -- -- FIXME: What should we say about generating values from types other -- than Word8 etc? @@ -180,6 +149,7 @@ module System.Random -- * Random values of various types -- $uniform , Uniform(..) + , uniformListM , UniformRange(..) , Random(..) @@ -246,56 +216,48 @@ mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 -- | The class 'RandomGen' provides a common interface to random number -- generators. -{-# DEPRECATED next "Use genWord32[R] or genWord64[R]" #-} -{-# DEPRECATED genRange "Use genWord32[R] or genWord64[R]" #-} +{-# DEPRECATED next "No longer used" #-} +{-# DEPRECATED genRange "No longer used" #-} class RandomGen g where - {-# MINIMAL (next,genRange)|((genWord32|genWord32R),(genWord64|genWord64R)) #-} + {-# MINIMAL split,(genWord32|genWord64|(next,genRange)) #-} -- |The 'next' operation returns an 'Int' that is uniformly -- distributed in the range returned by 'genRange' (including both -- end points), and a new generator. Using 'next' is inefficient as -- all operations go via 'Integer'. See -- [here](https://alexey.kuleshevi.ch/blog/2019/12/21/random-benchmarks) - -- for more details. It is thus deprecated. If you need random - -- values from other types you will need to construct a suitable - -- conversion function. + -- for more details. It is thus deprecated. next :: g -> (Int, g) - next g = (minR + fromIntegral w, g') where - (minR, maxR) = genRange g - range = fromIntegral $ maxR - minR -#if WORD_SIZE_IN_BITS == 32 - (w, g') = genWord32R range g -#elif WORD_SIZE_IN_BITS == 64 - (w, g') = genWord64R range g -#else --- https://hackage.haskell.org/package/ghc-prim-0.5.3/docs/GHC-Prim.html#g:1 --- GHC always implements Int using the primitive type Int#, whose size equals --- the MachDeps.h constant WORD_SIZE_IN_BITS. [...] Currently GHC itself has --- only 32-bit and 64-bit variants [...]. -# error unsupported WORD_SIZE_IN_BITS -#endif + next g = runGenState g (uniformR (genRange g)) genWord8 :: g -> (Word8, g) - genWord8 = first fromIntegral . genWord32R (fromIntegral (maxBound :: Word8)) + genWord8 = first fromIntegral . genWord32 genWord16 :: g -> (Word16, g) - genWord16 = first fromIntegral . genWord32R (fromIntegral (maxBound :: Word16)) + genWord16 = first fromIntegral . genWord32 genWord32 :: g -> (Word32, g) - genWord32 = genWord32R maxBound + genWord32 = randomIvalIntegral (minBound, maxBound) + -- Once `next` is removed, this implementation should be used instead: + -- first fromIntegral . genWord64 genWord64 :: g -> (Word64, g) - genWord64 = genWord64R maxBound + genWord64 g = + case genWord32 g of + (l32, g') -> + case genWord32 g' of + (h32, g'') -> + ((fromIntegral h32 `unsafeShiftL` 32) .|. fromIntegral l32, g'') genWord32R :: Word32 -> g -> (Word32, g) - genWord32R m = randomIvalIntegral (minBound, m) + genWord32R m g = runGenState g (bitmaskWithRejectionM uniformWord32 m) genWord64R :: Word64 -> g -> (Word64, g) - genWord64R m = randomIvalIntegral (minBound, m) + genWord64R m g = runGenState g (bitmaskWithRejectionM uniformWord64 m) genByteArray :: Int -> g -> (ByteArray, g) genByteArray n g = runPureGenST g $ uniformByteArrayPrim n - {-# INLINE genByteArray #-} + {-# INLINE genByteArray #-} -- |The 'genRange' operation yields the range of values returned by -- the generator. -- @@ -322,7 +284,7 @@ class RandomGen g where class Monad m => MonadRandom g m where data Frozen g :: * - {-# MINIMAL freezeGen,thawGen,(uniformWord32R|uniformWord32),(uniformWord64R|uniformWord64) #-} + {-# MINIMAL freezeGen,thawGen,(uniformWord32|uniformWord64) #-} thawGen :: Frozen g -> m g freezeGen :: g -> m (Frozen g) @@ -334,13 +296,16 @@ class Monad m => MonadRandom g m where uniformWord64R = bitmaskWithRejection64M uniformWord8 :: g -> m Word8 - uniformWord8 = fmap fromIntegral . uniformWord32R (fromIntegral (maxBound :: Word8)) + uniformWord8 = fmap fromIntegral . uniformWord32 uniformWord16 :: g -> m Word16 - uniformWord16 = fmap fromIntegral . uniformWord32R (fromIntegral (maxBound :: Word16)) + uniformWord16 = fmap fromIntegral . uniformWord32 uniformWord32 :: g -> m Word32 - uniformWord32 = uniformWord32R maxBound + uniformWord32 = fmap fromIntegral . uniformWord64 uniformWord64 :: g -> m Word64 - uniformWord64 = uniformWord64R maxBound + uniformWord64 g = do + l32 <- uniformWord32 g + h32 <- uniformWord32 g + pure (unsafeShiftL (fromIntegral h32) 32 .|. fromIntegral l32) uniformByteArray :: Int -> g -> m ByteArray default uniformByteArray :: PrimMonad m => Int -> g -> m ByteArray uniformByteArray = uniformByteArrayPrim @@ -354,6 +319,8 @@ withGenM fg action = do fg' <- freezeGen g pure (res, fg') +uniformListM :: (MonadRandom g m, Uniform a) => g -> Int -> m [a] +uniformListM gen n = replicateM n (uniform gen) -- | This function will efficiently generate a sequence of random bytes in a platform -- independent manner. Memory allocated will be pinned, so it is safe to use for FFI @@ -470,7 +437,8 @@ runGenStateT_ :: (RandomGen g, Functor f) => g -> (PureGen g -> StateT g f a) -> runGenStateT_ g = fmap fst . runGenStateT g -- | This is a wrapper wround pure generator that can be used in an effectful environment. --- It is safe in presence of concurrency since all operations are performed atomically. +-- It is safe in presence of exceptions and concurrency since all operations are performed +-- atomically. -- -- @since 1.2 newtype MutGen s g = MutGenI (MutVar s g) @@ -739,7 +707,7 @@ instance Random Integer where randomM g = uniformR (toInteger (minBound::Int), toInteger (maxBound::Int)) g instance UniformRange Integer where - --uniformR ival g = randomIvalInteger ival g -- FIXME + --niformR ival g = randomIvalInteger ival g -- FIXME instance Random Int8 where randomM = uniform From dc6257b39face258b83e0dd80fbac86bd1235586 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Mon, 30 Mar 2020 03:04:37 +0300 Subject: [PATCH 068/170] Adopt previous Integer range generator for UniformRange instance. Improve tests and coverage --- System/Random.hs | 160 +++++++++++++++++++------------------ random.cabal | 2 +- tests/Spec.hs | 178 ++++++++++++++++++++++++++++++++---------- tests/Spec/Bitmask.hs | 5 +- tests/Spec/Range.hs | 19 ++++- 5 files changed, 234 insertions(+), 130 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 862ae3841..9a0cb74ce 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -160,9 +160,6 @@ module System.Random -- * References -- $references - - -- * Internals - , bitmaskWithRejection -- FIXME Export this in a better way, e.g. in System.Random.Impl or something like that ) where import Control.Arrow @@ -185,11 +182,10 @@ import Foreign.C.Types import Foreign.Marshal.Alloc (alloca) import Foreign.Ptr (plusPtr) import Foreign.Storable (peekByteOff, pokeByteOff) -import GHC.Exts (Ptr(..)) +import GHC.Exts import GHC.ForeignPtr import System.IO.Unsafe (unsafePerformIO) import qualified System.Random.SplitMix as SM -import GHC.Base import GHC.Word @@ -633,7 +629,6 @@ programmer to extract random values of a variety of types. Minimal complete definition: 'randomR' and 'random'. -} -{-# DEPRECATED randomR "In favor of `uniformR`" #-} {-# DEPRECATED randomRIO "In favor of `uniformR`" #-} {-# DEPRECATED randomIO "In favor of `uniformR`" #-} class Random a where @@ -707,31 +702,35 @@ instance Random Integer where randomM g = uniformR (toInteger (minBound::Int), toInteger (maxBound::Int)) g instance UniformRange Integer where - --niformR ival g = randomIvalInteger ival g -- FIXME + uniformR = uniformIntegerM instance Random Int8 where randomM = uniform instance Uniform Int8 where uniform = fmap (fromIntegral :: Word8 -> Int8) . uniformWord8 instance UniformRange Int8 where + uniformR = bitmaskWithRejectionRM (fromIntegral :: Int8 -> Word8) fromIntegral instance Random Int16 where randomM = uniform instance Uniform Int16 where uniform = fmap (fromIntegral :: Word16 -> Int16) . uniformWord16 instance UniformRange Int16 where + uniformR = bitmaskWithRejectionRM (fromIntegral :: Int16 -> Word16) fromIntegral instance Random Int32 where randomM = uniform instance Uniform Int32 where uniform = fmap (fromIntegral :: Word32 -> Int32) . uniformWord32 instance UniformRange Int32 where + uniformR = bitmaskWithRejectionRM (fromIntegral :: Int32 -> Word32) fromIntegral instance Random Int64 where randomM = uniform instance Uniform Int64 where uniform = fmap (fromIntegral :: Word64 -> Int64) . uniformWord64 instance UniformRange Int64 where + uniformR = bitmaskWithRejectionRM (fromIntegral :: Int64 -> Word64) fromIntegral instance Random Int where randomM = uniform @@ -742,6 +741,7 @@ instance Uniform Int where uniform = fmap (fromIntegral :: Word64 -> Int) . uniformWord64 #endif instance UniformRange Int where + uniformR = bitmaskWithRejectionRM (fromIntegral :: Int -> Word) fromIntegral instance Random Word where randomM = uniform @@ -753,25 +753,25 @@ instance Uniform Word where #endif instance UniformRange Word where {-# INLINE uniformR #-} - uniformR = bitmaskWithRejectionRM + uniformR = unsignedBitmaskWithRejectionRM instance Random Word8 where randomM = uniform instance Uniform Word8 where {-# INLINE uniform #-} - uniform = uniformWord8 + uniform = uniformWord8 instance UniformRange Word8 where {-# INLINE uniformR #-} - uniformR = bitmaskWithRejectionRM + uniformR = unsignedBitmaskWithRejectionRM instance Random Word16 where randomM = uniform instance Uniform Word16 where {-# INLINE uniform #-} - uniform = uniformWord16 + uniform = uniformWord16 instance UniformRange Word16 where {-# INLINE uniformR #-} - uniformR = bitmaskWithRejectionRM + uniformR = unsignedBitmaskWithRejectionRM instance Random Word32 where randomM = uniform @@ -780,7 +780,7 @@ instance Uniform Word32 where uniform = uniformWord32 instance UniformRange Word32 where {-# INLINE uniformR #-} - uniformR = bitmaskWithRejectionRM + uniformR = unsignedBitmaskWithRejectionRM instance Random Word64 where randomM = uniform @@ -789,111 +789,111 @@ instance Uniform Word64 where uniform = uniformWord64 instance UniformRange Word64 where {-# INLINE uniformR #-} - uniformR = bitmaskWithRejectionRM + uniformR = unsignedBitmaskWithRejectionRM instance Random CChar where randomM = uniform instance Uniform CChar where - uniform = fmap CChar . uniform + uniform = fmap CChar . uniform instance UniformRange CChar where uniformR (CChar b, CChar t) = fmap CChar . uniformR (b, t) instance Random CSChar where randomM = uniform instance Uniform CSChar where - uniform = fmap CSChar . uniform + uniform = fmap CSChar . uniform instance UniformRange CSChar where uniformR (CSChar b, CSChar t) = fmap CSChar . uniformR (b, t) instance Random CUChar where randomM = uniform instance Uniform CUChar where - uniform = fmap CUChar . uniform + uniform = fmap CUChar . uniform instance UniformRange CUChar where uniformR (CUChar b, CUChar t) = fmap CUChar . uniformR (b, t) instance Random CShort where randomM = uniform instance Uniform CShort where - uniform = fmap CShort . uniform + uniform = fmap CShort . uniform instance UniformRange CShort where uniformR (CShort b, CShort t) = fmap CShort . uniformR (b, t) instance Random CUShort where randomM = uniform instance Uniform CUShort where - uniform = fmap CUShort . uniform + uniform = fmap CUShort . uniform instance UniformRange CUShort where uniformR (CUShort b, CUShort t) = fmap CUShort . uniformR (b, t) instance Random CInt where randomM = uniform instance Uniform CInt where - uniform = fmap CInt . uniform + uniform = fmap CInt . uniform instance UniformRange CInt where uniformR (CInt b, CInt t) = fmap CInt . uniformR (b, t) instance Random CUInt where randomM = uniform instance Uniform CUInt where - uniform = fmap CUInt . uniform + uniform = fmap CUInt . uniform instance UniformRange CUInt where uniformR (CUInt b, CUInt t) = fmap CUInt . uniformR (b, t) instance Random CLong where randomM = uniform instance Uniform CLong where - uniform = fmap CLong . uniform + uniform = fmap CLong . uniform instance UniformRange CLong where uniformR (CLong b, CLong t) = fmap CLong . uniformR (b, t) instance Random CULong where randomM = uniform instance Uniform CULong where - uniform = fmap CULong . uniform + uniform = fmap CULong . uniform instance UniformRange CULong where uniformR (CULong b, CULong t) = fmap CULong . uniformR (b, t) instance Random CPtrdiff where randomM = uniform instance Uniform CPtrdiff where - uniform = fmap CPtrdiff . uniform + uniform = fmap CPtrdiff . uniform instance UniformRange CPtrdiff where uniformR (CPtrdiff b, CPtrdiff t) = fmap CPtrdiff . uniformR (b, t) instance Random CSize where randomM = uniform instance Uniform CSize where - uniform = fmap CSize . uniform + uniform = fmap CSize . uniform instance UniformRange CSize where uniformR (CSize b, CSize t) = fmap CSize . uniformR (b, t) instance Random CWchar where randomM = uniform instance Uniform CWchar where - uniform = fmap CWchar . uniform + uniform = fmap CWchar . uniform instance UniformRange CWchar where uniformR (CWchar b, CWchar t) = fmap CWchar . uniformR (b, t) instance Random CSigAtomic where randomM = uniform instance Uniform CSigAtomic where - uniform = fmap CSigAtomic . uniform + uniform = fmap CSigAtomic . uniform instance UniformRange CSigAtomic where uniformR (CSigAtomic b, CSigAtomic t) = fmap CSigAtomic . uniformR (b, t) instance Random CLLong where randomM = uniform instance Uniform CLLong where - uniform = fmap CLLong . uniform + uniform = fmap CLLong . uniform instance UniformRange CLLong where uniformR (CLLong b, CLLong t) = fmap CLLong . uniformR (b, t) instance Random CULLong where randomM = uniform instance Uniform CULLong where - uniform = fmap CULLong . uniform + uniform = fmap CULLong . uniform instance UniformRange CULLong where uniformR (CULLong b, CULLong t) = fmap CULLong . uniformR (b, t) @@ -907,21 +907,21 @@ instance UniformRange CIntPtr where instance Random CUIntPtr where randomM = uniform instance Uniform CUIntPtr where - uniform = fmap CUIntPtr . uniform + uniform = fmap CUIntPtr . uniform instance UniformRange CUIntPtr where uniformR (CUIntPtr b, CUIntPtr t) = fmap CUIntPtr . uniformR (b, t) instance Random CIntMax where randomM = uniform instance Uniform CIntMax where - uniform = fmap CIntMax . uniform + uniform = fmap CIntMax . uniform instance UniformRange CIntMax where uniformR (CIntMax b, CIntMax t) = fmap CIntMax . uniformR (b, t) instance Random CUIntMax where randomM = uniform instance Uniform CUIntMax where - uniform = fmap CUIntMax . uniform + uniform = fmap CUIntMax . uniform instance UniformRange CUIntMax where uniformR (CUIntMax b, CUIntMax t) = fmap CUIntMax . uniformR (b, t) @@ -930,7 +930,11 @@ instance Random Char where instance Uniform Char where uniform = uniformR (minBound, maxBound) instance UniformRange Char where - -- FIXME + uniformR (l, h) g = toChar <$> unsignedBitmaskWithRejectionRM (fromChar l, fromChar h) g + where + fromChar (C# c#) = W# (int2Word# (ord# c#)) + toChar (W# w#) = C# (chr# (word2Int# w#)) + instance Random Bool where randomM = uniform @@ -1039,21 +1043,6 @@ randomFloat rng = mask24 = twoto24 - 1 twoto24 = (2::Int32) ^ (24::Int32) --- CFloat/CDouble are basically the same as a Float/Double: --- instance Random CFloat where --- randomR = randomRFloating - -- random rng = case random rng of - -- (x,rng') -> (realToFrac (x::Float), rng') - --- instance Random CDouble where --- randomR = randomRFloating --- -- A MYSTERY: --- -- Presently, this is showing better performance than the Double instance: --- -- (And yet, if the Double instance uses randomFrac then its performance is much worse!) --- random = randomFrac --- -- random rng = case random rng of --- -- (x,rng') -> (realToFrac (x::Double), rng') - -- The two integer functions below take an [inclusive,inclusive] range. randomIvalIntegral :: (RandomGen g, Integral a) => (a, a) -> g -> (a, g) randomIvalIntegral (l,h) = randomIvalInteger (toInteger l, toInteger h) @@ -1085,47 +1074,55 @@ randomIvalInteger (l,h) rng (x,g') = next g v' = (v * b + (fromIntegral x - fromIntegral genlo)) - -bitmaskWithRejection :: - (RandomGen g, FiniteBits a, Num a, Ord a, Random a) +uniformIntegerM :: (MonadRandom g m) => (Integer, Integer) -> g -> m Integer +uniformIntegerM (l, h) gen + | l > h = uniformIntegerM (h, l) gen + | otherwise = do + v <- f 1 0 + pure (l + v `mod` k) + where + b = toInteger (maxBound :: Word64) + q = 1000 + k = h - l + 1 + magtgt = k * q + -- generate random values until we exceed the target magnitude + f mag v + | mag >= magtgt = pure v + | otherwise = do + x <- uniformWord64 gen + let v' = v * b + fromIntegral x + v' `seq` f (mag * b) v' + + +-- | This only works for unsigned integrals +unsignedBitmaskWithRejectionRM :: + (MonadRandom g m, FiniteBits a, Num a, Ord a, Uniform a) => (a, a) -> g - -> (a, g) -bitmaskWithRejection (bottom, top) - | bottom > top = bitmaskWithRejection (top, bottom) - | bottom == top = (,) top - | otherwise = first (bottom +) . go + -> m a +unsignedBitmaskWithRejectionRM (bottom, top) gen + | bottom > top = unsignedBitmaskWithRejectionRM (top, bottom) gen + | bottom == top = pure top + | otherwise = (bottom +) <$> bitmaskWithRejectionM uniform range gen where range = top - bottom - mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) - go g = - let (x, g') = random g - x' = x .&. mask - in if x' >= range - then go g' - else (x', g') -{-# INLINE bitmaskWithRejection #-} +{-# INLINE unsignedBitmaskWithRejectionRM #-} - --- FIXME This is likely incorrect for signed integrals. +-- | This works for signed integrals by explicit conversion to unsigned and abusing overflow bitmaskWithRejectionRM :: - (MonadRandom g m, FiniteBits a, Num a, Ord a, Random a) - => (a, a) + (Num a, Num b, Ord b, Ord a, FiniteBits a, MonadRandom g f, Uniform a) + => (b -> a) + -> (a -> b) + -> (b, b) -> g - -> m a -bitmaskWithRejectionRM (bottom, top) gen - | bottom > top = bitmaskWithRejectionRM (top, bottom) gen + -> f b +bitmaskWithRejectionRM toUnsigned fromUnsigned (bottom, top) gen + | bottom > top = bitmaskWithRejectionRM toUnsigned fromUnsigned (top, bottom) gen | bottom == top = pure top - | otherwise = (bottom +) <$> go - where - range = top - bottom - mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) - go = do - x <- randomM gen - let x' = x .&. mask - if x' >= range - then go - else pure x' + | otherwise = (bottom +) . fromUnsigned <$> + bitmaskWithRejectionM uniform range gen + where + range = toUnsigned top - toUnsigned bottom {-# INLINE bitmaskWithRejectionRM #-} bitmaskWithRejectionM :: (Ord a, FiniteBits a, Num a, MonadRandom g m) => (g -> m a) -> a -> g -> m a @@ -1138,6 +1135,7 @@ bitmaskWithRejectionM genUniform range gen = go if x' >= range then go else pure x' +{-# INLINE bitmaskWithRejectionM #-} bitmaskWithRejection32M :: MonadRandom g m => Word32 -> g -> m Word32 diff --git a/random.cabal b/random.cabal index 4d3c963da..a774c0c8c 100644 --- a/random.cabal +++ b/random.cabal @@ -72,7 +72,7 @@ test-suite spec main-is: Spec.hs hs-source-dirs: tests other-modules: - Spec.Bitmask + -- Spec.Bitmask Spec.Range Spec.Run diff --git a/tests/Spec.hs b/tests/Spec.hs index 6e40520b1..80d453273 100644 --- a/tests/Spec.hs +++ b/tests/Spec.hs @@ -1,62 +1,116 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} - +{-# OPTIONS_GHC -fno-warn-orphans #-} module Main (main) where -import Data.Word (Word32, Word64) +import Data.Coerce +import Data.Word +import Data.Int import System.Random -import Test.SmallCheck (monadic) import Test.Tasty -import Test.Tasty.ExpectedFailure (expectFail) import Test.Tasty.SmallCheck as SC +import Test.SmallCheck.Series as SC +import Data.Typeable +import Foreign.C.Types -import qualified Spec.Bitmask as Bitmask -import qualified Spec.Bitmask as Range +--import qualified Spec.Bitmask as Bitmask +import qualified Spec.Range as Range import qualified Spec.Run as Run main :: IO () -main = defaultMain $ testGroup "Spec" - [ bitmaskSpecWord32, bitmaskSpecWord64 - , rangeSpecWord32, rangeSpecDouble, rangeSpecFloat, rangeSpecInt +main = + defaultMain $ + testGroup + "Spec" + [ floatingSpec @Double + , floatingSpec @Float + , integralSpec @Word8 + , integralSpec @Word16 + , integralSpec @Word32 + , integralSpec @Word64 + , integralSpec @Word + , integralSpec @Int8 + , integralSpec @Int16 + , integralSpec @Int32 + , integralSpec @Int64 + , integralSpec @Int + , integralSpec @CChar + , integralSpec @CSChar + , integralSpec @CUChar + , integralSpec @CShort + , integralSpec @CUShort + , integralSpec @CInt + , integralSpec @CUInt + , integralSpec @CLong + , integralSpec @CULong + , integralSpec @CPtrdiff + , integralSpec @CSize + , integralSpec @CWchar + , integralSpec @CSigAtomic + , integralSpec @CLLong + , integralSpec @CULLong + , integralSpec @CIntPtr + , integralSpec @CUIntPtr + , integralSpec @CIntMax + , integralSpec @CUIntMax + , integralSpec @Integer + -- , bitmaskSpec @Word8 + -- , bitmaskSpec @Word16 + -- , bitmaskSpec @Word32 + -- , bitmaskSpec @Word64 + -- , bitmaskSpec @Word , runSpec ] -bitmaskSpecWord32 :: TestTree -bitmaskSpecWord32 = testGroup "bitmaskWithRejection (Word32)" - [ SC.testProperty "symmetric" $ seeded $ Bitmask.symmetric @StdGen @Word32 - , SC.testProperty "bounded" $ seeded $ Bitmask.bounded @StdGen @Word32 - , SC.testProperty "singleton" $ seeded $ Bitmask.singleton @StdGen @Word32 - ] +showsType :: forall t . Typeable t => ShowS +showsType = showsTypeRep (typeRep (Proxy :: Proxy t)) -bitmaskSpecWord64 :: TestTree -bitmaskSpecWord64 = testGroup "bitmaskWithRejection (Word64)" - [ SC.testProperty "symmetric" $ seeded $ Bitmask.symmetric @StdGen @Word64 - , SC.testProperty "bounded" $ seeded $ Bitmask.bounded @StdGen @Word64 - , SC.testProperty "singleton" $ seeded $ Bitmask.singleton @StdGen @Word64 - ] - -rangeSpecWord32 :: TestTree -rangeSpecWord32 = testGroup "uniformR (Word32)" - [ SC.testProperty "(Word32) symmetric" $ seeded $ Range.symmetric @StdGen @Word32 - , SC.testProperty "(Word32) bounded" $ seeded $ Range.bounded @StdGen @Word32 - , SC.testProperty "(Word32) singleton" $ seeded $ Range.singleton @StdGen @Word32 - ] +-- bitmaskSpec :: +-- forall a. +-- (SC.Serial IO a, Typeable a, Num a, Ord a, Random a, FiniteBits a, Show a) +-- => TestTree +-- bitmaskSpec = +-- testGroup ("bitmaskWithRejection (" ++ showsType @a ")") +-- [ SC.testProperty "symmetric" $ seeded $ Bitmask.symmetric @_ @a +-- , SC.testProperty "bounded" $ seeded $ Bitmask.bounded @_ @a +-- , SC.testProperty "singleton" $ seeded $ Bitmask.singleton @_ @a +-- ] -rangeSpecDouble :: TestTree -rangeSpecDouble = testGroup "uniformR (Double)" - [ SC.testProperty "(Double) uniform bounded" $ seeded $ Range.uniformBounded @StdGen @Double - ] +rangeSpec :: + forall a. + (SC.Serial IO a, Typeable a, Num a, Ord a, Random a, UniformRange a, Show a) + => TestTree +rangeSpec = + testGroup ("Range (" ++ showsType @a ")") + [ SC.testProperty "uniformR" $ seeded $ Range.uniformRangeWithin @_ @a + ] -rangeSpecFloat :: TestTree -rangeSpecFloat = testGroup "uniformR (Float)" - [ SC.testProperty "(Float) uniform bounded" $ seeded $ Range.uniformBounded @StdGen @Float - ] +integralSpec :: + forall a. + (SC.Serial IO a, Typeable a, Num a, Ord a, Random a, UniformRange a, Show a) + => TestTree +integralSpec = + testGroup ("(" ++ showsType @a ")") + [ SC.testProperty "symmetric" $ seeded $ Range.symmetric @_ @a + , SC.testProperty "bounded" $ seeded $ Range.bounded @_ @a + , SC.testProperty "singleton" $ seeded $ Range.singleton @_ @a + , rangeSpec @a + -- TODO: Add more tests + ] -rangeSpecInt :: TestTree -rangeSpecInt = testGroup "uniformR (Int)" - [ SC.testProperty "(Int) symmetric" $ seeded $ Range.symmetric @StdGen @Int - , expectFail $ SC.testProperty "(Int) bounded" $ seeded $ Range.bounded @StdGen @Int - , SC.testProperty "(Int) singleton" $ seeded $ Range.singleton @StdGen @Int - ] +floatingSpec :: + forall a. + (SC.Serial IO a, Typeable a, Num a, Ord a, Random a, UniformRange a, Show a) + => TestTree +floatingSpec = + testGroup ("(" ++ showsType @a ")") + [ SC.testProperty "uniformR" $ seeded $ Range.uniformRangeWithinExcluded @_ @a + -- TODO: Add more tests + ] runSpec :: TestTree runSpec = testGroup "runGenState_ and runPrimGenIO_" @@ -65,3 +119,43 @@ runSpec = testGroup "runGenState_ and runPrimGenIO_" -- | Create a StdGen instance from an Int and pass it to the given function. seeded :: (StdGen -> a) -> Int -> a seeded f = f . mkStdGen + + +instance Monad m => Serial m CChar where + series = coerce <$> (series :: Series m Int8) +instance Monad m => Serial m CSChar where + series = coerce <$> (series :: Series m Int8) +instance Monad m => Serial m CUChar where + series = coerce <$> (series :: Series m Word8) +instance Monad m => Serial m CShort where + series = coerce <$> (series :: Series m Int16) +instance Monad m => Serial m CUShort where + series = coerce <$> (series :: Series m Word16) +instance Monad m => Serial m CInt where + series = coerce <$> (series :: Series m Int32) +instance Monad m => Serial m CUInt where + series = coerce <$> (series :: Series m Word32) +instance Monad m => Serial m CLong where + series = coerce <$> (series :: Series m Int64) +instance Monad m => Serial m CULong where + series = coerce <$> (series :: Series m Word64) +instance Monad m => Serial m CPtrdiff where + series = coerce <$> (series :: Series m Int64) +instance Monad m => Serial m CSize where + series = coerce <$> (series :: Series m Word64) +instance Monad m => Serial m CWchar where + series = coerce <$> (series :: Series m Int32) +instance Monad m => Serial m CSigAtomic where + series = coerce <$> (series :: Series m Int32) +instance Monad m => Serial m CLLong where + series = coerce <$> (series :: Series m Int64) +instance Monad m => Serial m CULLong where + series = coerce <$> (series :: Series m Word64) +instance Monad m => Serial m CIntPtr where + series = coerce <$> (series :: Series m Int64) +instance Monad m => Serial m CUIntPtr where + series = coerce <$> (series :: Series m Word64) +instance Monad m => Serial m CIntMax where + series = coerce <$> (series :: Series m Int64) +instance Monad m => Serial m CUIntMax where + series = coerce <$> (series :: Series m Word64) diff --git a/tests/Spec/Bitmask.hs b/tests/Spec/Bitmask.hs index 1aa814dce..bd43505a0 100644 --- a/tests/Spec/Bitmask.hs +++ b/tests/Spec/Bitmask.hs @@ -1,4 +1,4 @@ -module Spec.Bitmask (symmetric, bounded, singleton, uniformBounded) where +module Spec.Bitmask (symmetric, bounded, singleton) where import Data.Bits import System.Random @@ -17,6 +17,3 @@ singleton :: (RandomGen g, FiniteBits a, Num a, Ord a, Random a) => g -> a -> Bo singleton g x = result == x where result = fst (bitmaskWithRejection (x, x) g) - -uniformBounded :: (RandomGen g, UniformRange a, Ord a) => g -> (a, a) -> Bool -uniformBounded g (l, r) = runGenState_ g (\g -> (uniformR (l, r) g >>= \result -> return ((min l r) <= result && result <= (max l r)))) diff --git a/tests/Spec/Range.hs b/tests/Spec/Range.hs index 09a65035a..4dd6aebe5 100644 --- a/tests/Spec/Range.hs +++ b/tests/Spec/Range.hs @@ -1,6 +1,11 @@ -module Spec.Range (symmetric, bounded, singleton) where +module Spec.Range + ( symmetric + , bounded + , singleton + , uniformRangeWithin + , uniformRangeWithinExcluded + ) where -import Data.Bits import System.Random symmetric :: (RandomGen g, Random a, Eq a) => g -> (a, a) -> Bool @@ -17,3 +22,13 @@ singleton :: (RandomGen g, Random a, Eq a) => g -> a -> Bool singleton g x = result == x where result = fst (randomR (x, x) g) + +uniformRangeWithin :: (RandomGen g, UniformRange a, Ord a) => g -> (a, a) -> Bool +uniformRangeWithin gen (l, r) = + runGenState_ gen $ \g -> + (\result -> min l r <= result && result <= max l r) <$> uniformR (l, r) g + +uniformRangeWithinExcluded :: (RandomGen g, UniformRange a, Ord a) => g -> (a, a) -> Bool +uniformRangeWithinExcluded gen (l, r) = + runGenState_ gen $ \g -> + (\result -> min l r <= result && (l == r || result < max l r)) <$> uniformR (l, r) g From c5c96c69ed8a630f1a659f831f74be554900cc64 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Mon, 30 Mar 2020 03:16:30 +0300 Subject: [PATCH 069/170] Instances and tests for CFloat and CDouble --- System/Random.hs | 57 ++++++++++++++++-------------------------------- tests/Spec.hs | 10 ++++++++- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 9a0cb74ce..87f5ea94f 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -925,6 +925,21 @@ instance Uniform CUIntMax where instance UniformRange CUIntMax where uniformR (CUIntMax b, CUIntMax t) = fmap CUIntMax . uniformR (b, t) +instance Random CFloat where + randomR (CFloat l, CFloat h) = first CFloat . randomR (l, h) + random = first CFloat . random + randomM = fmap CFloat . randomM +instance UniformRange CFloat where + uniformR (CFloat l, CFloat h) = fmap CFloat . uniformR (l, h) + +instance Random CDouble where + randomR (CDouble l, CDouble h) = first CDouble . randomR (l, h) + random = first CDouble . random + randomM = fmap CDouble . randomM +instance UniformRange CDouble where + uniformR (CDouble l, CDouble h) = fmap CDouble . uniformR (l, h) + + instance Random Char where randomM = uniform instance Uniform Char where @@ -950,16 +965,9 @@ instance UniformRange Bool where int2Bool 0 = False int2Bool _ = True -{-# INLINE randomRFloating #-} -randomRFloating :: (Fractional a, Num a, Ord a, Random a, RandomGen g) => (a, a) -> g -> (a, g) -randomRFloating (l,h) g - | l>h = randomRFloating (h,l) g - | otherwise = let (coef,g') = random g in - (2.0 * (0.5*l + coef * (0.5*h - 0.5*l)), g') -- avoid overflow - instance Random Double where - randomR = randomRFloating - random = randomDouble + randomR r g = runGenState g (uniformR r) + random g = runGenState g randomM randomM = uniformR (0, 1) instance UniformRange Double where @@ -997,23 +1005,11 @@ foreign import prim "stg_word64ToDoubleyg" stgWord64ToDouble :: Word64# -> Double# #endif -randomDouble :: RandomGen b => b -> (Double, b) -randomDouble rng = - case random rng of - (x,rng') -> - -- We use 53 bits of randomness corresponding to the 53 bit significand: - ((fromIntegral (mask53 .&. (x::Int64)) :: Double) - / fromIntegral twoto53, rng') - where - twoto53 = (2::Int64) ^ (53::Int64) - mask53 = twoto53 - 1 - instance Random Float where - randomR = randomRFloating - random = randomFloat + randomR r g = runGenState g (uniformR r) + random g = runGenState g randomM randomM = uniformR (0, 1) - instance UniformRange Float where uniformR (l, h) g = do w32 <- uniformWord32 g @@ -1028,21 +1024,6 @@ word32ToFloatInUnitInterval w32 = between1and2 - 1.0 between1and2 = castWord32ToFloat $ (w32 `unsafeShiftR` 9) .|. 0x3f800000 {-# INLINE word32ToFloatInUnitInterval #-} -randomFloat :: RandomGen b => b -> (Float, b) -randomFloat rng = - -- TODO: Faster to just use 'next' IF it generates enough bits of randomness. - case random rng of - (x,rng') -> - -- We use 24 bits of randomness corresponding to the 24 bit significand: - ((fromIntegral (mask24 .&. (x::Int32)) :: Float) - / fromIntegral twoto24, rng') - -- Note, encodeFloat is another option, but I'm not seeing slightly - -- worse performance with the following [2011.06.25]: --- (encodeFloat rand (-24), rng') - where - mask24 = twoto24 - 1 - twoto24 = (2::Int32) ^ (24::Int32) - -- The two integer functions below take an [inclusive,inclusive] range. randomIvalIntegral :: (RandomGen g, Integral a) => (a, a) -> g -> (a, g) randomIvalIntegral (l,h) = randomIvalInteger (toInteger l, toInteger h) diff --git a/tests/Spec.hs b/tests/Spec.hs index 80d453273..bb632cfea 100644 --- a/tests/Spec.hs +++ b/tests/Spec.hs @@ -28,6 +28,8 @@ main = "Spec" [ floatingSpec @Double , floatingSpec @Float + , floatingSpec @CDouble + , floatingSpec @CFloat , integralSpec @Word8 , integralSpec @Word16 , integralSpec @Word32 @@ -58,6 +60,8 @@ main = , integralSpec @CIntMax , integralSpec @CUIntMax , integralSpec @Integer + , rangeSpec @Char + , rangeSpec @Bool -- , bitmaskSpec @Word8 -- , bitmaskSpec @Word16 -- , bitmaskSpec @Word32 @@ -82,7 +86,7 @@ showsType = showsTypeRep (typeRep (Proxy :: Proxy t)) rangeSpec :: forall a. - (SC.Serial IO a, Typeable a, Num a, Ord a, Random a, UniformRange a, Show a) + (SC.Serial IO a, Typeable a, Ord a, Random a, UniformRange a, Show a) => TestTree rangeSpec = testGroup ("Range (" ++ showsType @a ")") @@ -121,6 +125,10 @@ seeded :: (StdGen -> a) -> Int -> a seeded f = f . mkStdGen +instance Monad m => Serial m CFloat where + series = coerce <$> (series :: Series m Float) +instance Monad m => Serial m CDouble where + series = coerce <$> (series :: Series m Double) instance Monad m => Serial m CChar where series = coerce <$> (series :: Series m Int8) instance Monad m => Serial m CSChar where From 30ede52361c8c102b9802a6403a0ff0136e9568d Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Mon, 30 Mar 2020 03:36:42 +0300 Subject: [PATCH 070/170] Fix doctests and stop an example writing a file in current dir --- System/Random.hs | 23 ++++++++++++++++------- random.cabal | 3 ++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 87f5ea94f..a43512ffd 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -66,7 +66,7 @@ -- >>> fst $ stepGen $ snd $ stepGen (PCGen 17 29) -- 3288430965 -- --- Once implemented it can made an instance of `RandomGen`: +-- Once implemented an instance of `RandomGen` can be created: -- -- >>> :{ -- instance RandomGen PCGen where @@ -74,6 +74,12 @@ -- split _ = error "This PRNG is not splittable" -- :} -- +-- Note, that depending on thow many bits of randomness your RNG produces in one iteration +-- you might need to implement a different function in the `RandomGen` class. +-- +-- Once implemented, every pure generator can be used with `MonadRandom` by the means of +-- `PureGen` and state transformers. See an example below of such use case. +-- -- [/Example for RNG Users:/] -- -- Suppose you want to simulate a number of rolls from a dice (yes I know it's a @@ -206,7 +212,6 @@ mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 -- >>> import Control.Monad (replicateM) -- >>> import Data.Bits -- >>> import Data.Word --- >>> import System.IO (IOMode(WriteMode), hPutStr, withBinaryFile) -- >>> :set -XFlexibleContexts -- >>> :set -fno-warn-missing-methods @@ -483,12 +488,16 @@ runMutGenST g action = runST $ do runMutGenST_ :: RandomGen g => g -> (forall s . MutGen s g -> ST s a) -> a runMutGenST_ g action = fst $ runMutGenST g action --- | Functions like 'runMutGenIO' are necessary for example if you --- wish to write a function like +-- | Both `PrimGen` and `MutGen` and their corresponding functions like 'runPrimGenIO' are +-- necessary when generation of random values happens in `IO` and especially when dealing +-- with exception handling and resource allocation, which is where `StateT` should never be +-- used. For example writing a random number of bytes into a temporary file: -- --- >>> let ioGen gen = withBinaryFile "foo.txt" WriteMode $ \h -> ((uniform gen) :: IO Word32) >>= (hPutStr h . show) +-- >>> import UnliftIO.Temporary (withSystemTempFile) +-- >>> import Data.ByteString (hPutStr) +-- >>> let ioGen g = withSystemTempFile "foo.bin" $ \_ h -> uniformR (0, 100) g >>= flip uniformByteStringPrim g >>= hPutStr h -- --- and then run it +-- and then run it: -- -- >>> runMutGenIO_ (mkStdGen 1729) ioGen -- @@ -598,7 +607,7 @@ mkStdGen s = SM.mkSMGen $ fromIntegral s -- @UniformRange@. We could try to generate values that @a <= x <= b@ -- But to do that we need to know number of elements of tuple's second -- type parameter @b@ which we don't have. --- +-- -- Or type could have no order at all. Take for example -- angle. Defining @Uniform@ instance is again straghtforward: just -- generate value in @[0,2π)@ range. But for any two pair of angles diff --git a/random.cabal b/random.cabal index a774c0c8c..57b53b2a8 100644 --- a/random.cabal +++ b/random.cabal @@ -65,7 +65,8 @@ test-suite doctests build-depends: base -any, doctest >=0.15, - random -any + random -any, + unliftio -any test-suite spec type: exitcode-stdio-1.0 From b342e251f1c3c9f502a93e9b804258a592047f21 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Mon, 30 Mar 2020 15:31:49 +0200 Subject: [PATCH 071/170] Also test Char and Bool --- tests/Spec.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Spec.hs b/tests/Spec.hs index bb632cfea..aa764cdd0 100644 --- a/tests/Spec.hs +++ b/tests/Spec.hs @@ -40,6 +40,8 @@ main = , integralSpec @Int32 , integralSpec @Int64 , integralSpec @Int + , integralSpec @Char + , integralSpec @Bool , integralSpec @CChar , integralSpec @CSChar , integralSpec @CUChar @@ -95,7 +97,7 @@ rangeSpec = integralSpec :: forall a. - (SC.Serial IO a, Typeable a, Num a, Ord a, Random a, UniformRange a, Show a) + (SC.Serial IO a, Typeable a, Ord a, Random a, UniformRange a, Show a) => TestTree integralSpec = testGroup ("(" ++ showsType @a ")") From 64c0667dec671fe7add0c1f2b8a97aa0b252cc11 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Mon, 30 Mar 2020 15:42:11 +0200 Subject: [PATCH 072/170] Make bitmaskWithRejectionM inclusive --- System/Random.hs | 4 ++-- tests/Spec.hs | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index a43512ffd..868a7bf04 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -93,7 +93,7 @@ -- :} -- -- >>> rolls 20 --- [1,1,5,3,3,3,2,4,3,2,3,3,4,5,1,1,5,1,2,4] +-- [1,1,5,3,6,3,3,2,4,3,2,3,3,4,6,6,5,6,1,1] -- -- FIXME: What should we say about generating values from types other -- than Word8 etc? @@ -1122,7 +1122,7 @@ bitmaskWithRejectionM genUniform range gen = go go = do x <- genUniform gen let x' = x .&. mask - if x' >= range + if x' > range then go else pure x' {-# INLINE bitmaskWithRejectionM #-} diff --git a/tests/Spec.hs b/tests/Spec.hs index aa764cdd0..55a3d24a0 100644 --- a/tests/Spec.hs +++ b/tests/Spec.hs @@ -62,8 +62,6 @@ main = , integralSpec @CIntMax , integralSpec @CUIntMax , integralSpec @Integer - , rangeSpec @Char - , rangeSpec @Bool -- , bitmaskSpec @Word8 -- , bitmaskSpec @Word16 -- , bitmaskSpec @Word32 From 621c93e0a7fba187fe441a7fd7dfe96026cc6792 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Tue, 31 Mar 2020 09:37:19 +0200 Subject: [PATCH 073/170] Mark all bitmask functions with signed/unsigned --- System/Random.hs | 43 ++++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 868a7bf04..9c58048e7 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -250,10 +250,10 @@ class RandomGen g where ((fromIntegral h32 `unsafeShiftL` 32) .|. fromIntegral l32, g'') genWord32R :: Word32 -> g -> (Word32, g) - genWord32R m g = runGenState g (bitmaskWithRejectionM uniformWord32 m) + genWord32R m g = runGenState g (unsignedBitmaskWithRejectionM uniformWord32 m) genWord64R :: Word64 -> g -> (Word64, g) - genWord64R m g = runGenState g (bitmaskWithRejectionM uniformWord64 m) + genWord64R m g = runGenState g (unsignedBitmaskWithRejectionM uniformWord64 m) genByteArray :: Int -> g -> (ByteArray, g) genByteArray n g = runPureGenST g $ uniformByteArrayPrim n @@ -291,10 +291,10 @@ class Monad m => MonadRandom g m where freezeGen :: g -> m (Frozen g) -- | Generate `Word32` up to and including the supplied max value uniformWord32R :: Word32 -> g -> m Word32 - uniformWord32R = bitmaskWithRejection32M + uniformWord32R = unsignedBitmaskWithRejectionM uniformWord32 -- | Generate `Word64` up to and including the supplied max value uniformWord64R :: Word64 -> g -> m Word64 - uniformWord64R = bitmaskWithRejection64M + uniformWord64R = unsignedBitmaskWithRejectionM uniformWord64 uniformWord8 :: g -> m Word8 uniformWord8 = fmap fromIntegral . uniformWord32 @@ -718,28 +718,28 @@ instance Random Int8 where instance Uniform Int8 where uniform = fmap (fromIntegral :: Word8 -> Int8) . uniformWord8 instance UniformRange Int8 where - uniformR = bitmaskWithRejectionRM (fromIntegral :: Int8 -> Word8) fromIntegral + uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int8 -> Word8) fromIntegral instance Random Int16 where randomM = uniform instance Uniform Int16 where uniform = fmap (fromIntegral :: Word16 -> Int16) . uniformWord16 instance UniformRange Int16 where - uniformR = bitmaskWithRejectionRM (fromIntegral :: Int16 -> Word16) fromIntegral + uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int16 -> Word16) fromIntegral instance Random Int32 where randomM = uniform instance Uniform Int32 where uniform = fmap (fromIntegral :: Word32 -> Int32) . uniformWord32 instance UniformRange Int32 where - uniformR = bitmaskWithRejectionRM (fromIntegral :: Int32 -> Word32) fromIntegral + uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int32 -> Word32) fromIntegral instance Random Int64 where randomM = uniform instance Uniform Int64 where uniform = fmap (fromIntegral :: Word64 -> Int64) . uniformWord64 instance UniformRange Int64 where - uniformR = bitmaskWithRejectionRM (fromIntegral :: Int64 -> Word64) fromIntegral + uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int64 -> Word64) fromIntegral instance Random Int where randomM = uniform @@ -750,7 +750,7 @@ instance Uniform Int where uniform = fmap (fromIntegral :: Word64 -> Int) . uniformWord64 #endif instance UniformRange Int where - uniformR = bitmaskWithRejectionRM (fromIntegral :: Int -> Word) fromIntegral + uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int -> Word) fromIntegral instance Random Word where randomM = uniform @@ -1093,30 +1093,30 @@ unsignedBitmaskWithRejectionRM :: unsignedBitmaskWithRejectionRM (bottom, top) gen | bottom > top = unsignedBitmaskWithRejectionRM (top, bottom) gen | bottom == top = pure top - | otherwise = (bottom +) <$> bitmaskWithRejectionM uniform range gen + | otherwise = (bottom +) <$> unsignedBitmaskWithRejectionM uniform range gen where range = top - bottom {-# INLINE unsignedBitmaskWithRejectionRM #-} -- | This works for signed integrals by explicit conversion to unsigned and abusing overflow -bitmaskWithRejectionRM :: +signedBitmaskWithRejectionRM :: (Num a, Num b, Ord b, Ord a, FiniteBits a, MonadRandom g f, Uniform a) => (b -> a) -> (a -> b) -> (b, b) -> g -> f b -bitmaskWithRejectionRM toUnsigned fromUnsigned (bottom, top) gen - | bottom > top = bitmaskWithRejectionRM toUnsigned fromUnsigned (top, bottom) gen +signedBitmaskWithRejectionRM toUnsigned fromUnsigned (bottom, top) gen + | bottom > top = signedBitmaskWithRejectionRM toUnsigned fromUnsigned (top, bottom) gen | bottom == top = pure top | otherwise = (bottom +) . fromUnsigned <$> - bitmaskWithRejectionM uniform range gen + unsignedBitmaskWithRejectionM uniform range gen where range = toUnsigned top - toUnsigned bottom -{-# INLINE bitmaskWithRejectionRM #-} +{-# INLINE signedBitmaskWithRejectionRM #-} -bitmaskWithRejectionM :: (Ord a, FiniteBits a, Num a, MonadRandom g m) => (g -> m a) -> a -> g -> m a -bitmaskWithRejectionM genUniform range gen = go +unsignedBitmaskWithRejectionM :: (Ord a, FiniteBits a, Num a, MonadRandom g m) => (g -> m a) -> a -> g -> m a +unsignedBitmaskWithRejectionM genUniform range gen = go where mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) go = do @@ -1125,14 +1125,7 @@ bitmaskWithRejectionM genUniform range gen = go if x' > range then go else pure x' -{-# INLINE bitmaskWithRejectionM #-} - - -bitmaskWithRejection32M :: MonadRandom g m => Word32 -> g -> m Word32 -bitmaskWithRejection32M = bitmaskWithRejectionM uniformWord32 - -bitmaskWithRejection64M :: MonadRandom g m => Word64 -> g -> m Word64 -bitmaskWithRejection64M = bitmaskWithRejectionM uniformWord64 +{-# INLINE unsignedBitmaskWithRejectionM #-} -- The global random number generator From c24c44333a2be96ee57f433df60604c2913e0169 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Tue, 31 Mar 2020 09:50:33 +0200 Subject: [PATCH 074/170] Explain why range is computed correctly --- System/Random.hs | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/System/Random.hs b/System/Random.hs index 868a7bf04..993ad823e 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1112,6 +1112,7 @@ bitmaskWithRejectionRM toUnsigned fromUnsigned (bottom, top) gen | otherwise = (bottom +) . fromUnsigned <$> bitmaskWithRejectionM uniform range gen where + -- This works in all cases, see Appendix 1 at the end of the file. range = toUnsigned top - toUnsigned bottom {-# INLINE bitmaskWithRejectionRM #-} @@ -1185,3 +1186,68 @@ Conference on Object Oriented Programming Systems Languages & Applications https://doi.org/10.1145/2660193.2660195 -} + +-- Appendix 1. +-- +-- @top@ and @bottom@ are signed integers of bit width @n@. @toUnsigned@ +-- converts a signed integer to an unsigned number of the same bit width @n@. +-- +-- range = toUnsigned top - toUnsigned bottom +-- +-- This works out correctly thanks to modular arithmetic. Conceptually, +-- +-- toUnsigned x | x >= 0 = x +-- toUnsigned x | x < 0 = 2^n + x +-- +-- The following combinations are possible: +-- +-- 1. @bottom >= 0@ and @top >= 0@ +-- 2. @bottom < 0@ and @top >= 0@ +-- 3. @bottom < 0@ and @top < 0@ +-- +-- Note that @bottom >= 0@ and @top < 0@ is impossible because of the +-- invariant @bottom < top@. +-- +-- For any signed integer @i@ of width @n@, we have: +-- +-- -2^(n-1) <= i <= 2^(n-1) - 1 +-- +-- Considering each combination in turn, we have +-- +-- 1. @bottom >= 0@ and @top >= 0@ +-- +-- range = (toUnsigned top - toUnsigned bottom) `mod` 2^n +-- --^ top >= 0, so toUnsigned top == top +-- --^ bottom >= 0, so toUnsigned bottom == bottom +-- = (top - bottom) `mod` 2^n +-- --^ top <= 2^(n-1) - 1 and bottom >= 0 +-- --^ top - bottom <= 2^(n-1) - 1 +-- --^ 0 < top - bottom <= 2^(n-1) - 1 +-- = top - bottom +-- +-- 2. @bottom < 0@ and @top >= 0@ +-- +-- range = (toUnsigned top - toUnsigned bottom) `mod` 2^n +-- --^ top >= 0, so toUnsigned top == top +-- --^ bottom < 0, so toUnsigned bottom == 2^n + bottom +-- = (top - (2^n + bottom)) `mod` 2^n +-- --^ summand -2^n cancels out in calculation modulo 2^n +-- = (top - bottom) `mod` 2^n +-- --^ top <= 2^(n-1) - 1 and bottom >= -2^(n-1) +-- --^ top - bottom <= (2^(n-1) - 1) - (-2^(n-1)) = 2^n - 1 +-- --^ 0 < top - bottom <= 2^n - 1 +-- = top - bottom +-- +-- 3. @bottom < 0@ and @top < 0@ +-- +-- range = (toUnsigned top - toUnsigned bottom) `mod` 2^n +-- --^ top < 0, so toUnsigned top == 2^n + top +-- --^ bottom < 0, so toUnsigned bottom == 2^n + bottom +-- = ((2^n + top) - (2^n + bottom)) `mod` 2^n +-- --^ summand 2^n cancels out in calculation modulo 2^n +-- = (top - bottom) `mod` 2^n +-- --^ top <= -1 +-- --^ bottom >= -2^(n-1) +-- --^ top - bottom <= -1 - (-2^(n-1)) = 2^(n-1) - 1 +-- --^ 0 < top - bottom <= 2^(n-1) - 1 +-- = top - bottom From bbcee73fbb3110ceff2fb458f7a59a5b4b036862 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Fri, 3 Apr 2020 05:39:43 +0300 Subject: [PATCH 075/170] Add instance for CBool and ensure correct instances for foreign types in tests --- System/Random.hs | 6 ++++++ tests/Spec.hs | 44 +++++++++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 2dc2e77fb..f2df903a3 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -800,6 +800,12 @@ instance UniformRange Word64 where {-# INLINE uniformR #-} uniformR = unsignedBitmaskWithRejectionRM +instance Random CBool where + randomM = uniform +instance Uniform CBool where + uniform = fmap CBool . uniform +instance UniformRange CBool where + uniformR (CBool b, CBool t) = fmap CBool . uniformR (b, t) instance Random CChar where randomM = uniform diff --git a/tests/Spec.hs b/tests/Spec.hs index 55a3d24a0..36f8f73a1 100644 --- a/tests/Spec.hs +++ b/tests/Spec.hs @@ -1,4 +1,5 @@ {-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} @@ -17,6 +18,8 @@ import Test.SmallCheck.Series as SC import Data.Typeable import Foreign.C.Types +#include "HsBaseConfig.h" + --import qualified Spec.Bitmask as Bitmask import qualified Spec.Range as Range import qualified Spec.Run as Run @@ -42,6 +45,7 @@ main = , integralSpec @Int , integralSpec @Char , integralSpec @Bool + , integralSpec @CBool , integralSpec @CChar , integralSpec @CSChar , integralSpec @CUChar @@ -129,41 +133,43 @@ instance Monad m => Serial m CFloat where series = coerce <$> (series :: Series m Float) instance Monad m => Serial m CDouble where series = coerce <$> (series :: Series m Double) +instance Monad m => Serial m CBool where + series = coerce <$> (series :: Series m HTYPE_BOOL) instance Monad m => Serial m CChar where - series = coerce <$> (series :: Series m Int8) + series = coerce <$> (series :: Series m HTYPE_CHAR) instance Monad m => Serial m CSChar where - series = coerce <$> (series :: Series m Int8) + series = coerce <$> (series :: Series m HTYPE_SIGNED_CHAR) instance Monad m => Serial m CUChar where - series = coerce <$> (series :: Series m Word8) + series = coerce <$> (series :: Series m HTYPE_UNSIGNED_CHAR) instance Monad m => Serial m CShort where - series = coerce <$> (series :: Series m Int16) + series = coerce <$> (series :: Series m HTYPE_SHORT) instance Monad m => Serial m CUShort where - series = coerce <$> (series :: Series m Word16) + series = coerce <$> (series :: Series m HTYPE_UNSIGNED_SHORT) instance Monad m => Serial m CInt where - series = coerce <$> (series :: Series m Int32) + series = coerce <$> (series :: Series m HTYPE_INT) instance Monad m => Serial m CUInt where - series = coerce <$> (series :: Series m Word32) + series = coerce <$> (series :: Series m HTYPE_UNSIGNED_INT) instance Monad m => Serial m CLong where - series = coerce <$> (series :: Series m Int64) + series = coerce <$> (series :: Series m HTYPE_LONG) instance Monad m => Serial m CULong where - series = coerce <$> (series :: Series m Word64) + series = coerce <$> (series :: Series m HTYPE_UNSIGNED_LONG) instance Monad m => Serial m CPtrdiff where - series = coerce <$> (series :: Series m Int64) + series = coerce <$> (series :: Series m HTYPE_PTRDIFF_T) instance Monad m => Serial m CSize where - series = coerce <$> (series :: Series m Word64) + series = coerce <$> (series :: Series m HTYPE_SIZE_T) instance Monad m => Serial m CWchar where - series = coerce <$> (series :: Series m Int32) + series = coerce <$> (series :: Series m HTYPE_WCHAR_T) instance Monad m => Serial m CSigAtomic where - series = coerce <$> (series :: Series m Int32) + series = coerce <$> (series :: Series m HTYPE_SIG_ATOMIC_T) instance Monad m => Serial m CLLong where - series = coerce <$> (series :: Series m Int64) + series = coerce <$> (series :: Series m HTYPE_LONG_LONG) instance Monad m => Serial m CULLong where - series = coerce <$> (series :: Series m Word64) + series = coerce <$> (series :: Series m HTYPE_UNSIGNED_LONG_LONG) instance Monad m => Serial m CIntPtr where - series = coerce <$> (series :: Series m Int64) + series = coerce <$> (series :: Series m HTYPE_INTPTR_T) instance Monad m => Serial m CUIntPtr where - series = coerce <$> (series :: Series m Word64) + series = coerce <$> (series :: Series m HTYPE_UINTPTR_T) instance Monad m => Serial m CIntMax where - series = coerce <$> (series :: Series m Int64) + series = coerce <$> (series :: Series m HTYPE_INTMAX_T) instance Monad m => Serial m CUIntMax where - series = coerce <$> (series :: Series m Word64) + series = coerce <$> (series :: Series m HTYPE_UINTMAX_T) From 52784a58c739690deb8048dffe72c60bc8f02532 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Fri, 3 Apr 2020 05:40:44 +0300 Subject: [PATCH 076/170] Remove dead code --- random.cabal | 1 - tests/Spec.hs | 1 - tests/Spec/Bitmask.hs | 19 ------------------- 3 files changed, 21 deletions(-) delete mode 100644 tests/Spec/Bitmask.hs diff --git a/random.cabal b/random.cabal index 57b53b2a8..06536bd75 100644 --- a/random.cabal +++ b/random.cabal @@ -73,7 +73,6 @@ test-suite spec main-is: Spec.hs hs-source-dirs: tests other-modules: - -- Spec.Bitmask Spec.Range Spec.Run diff --git a/tests/Spec.hs b/tests/Spec.hs index 36f8f73a1..800b5d1b1 100644 --- a/tests/Spec.hs +++ b/tests/Spec.hs @@ -20,7 +20,6 @@ import Foreign.C.Types #include "HsBaseConfig.h" ---import qualified Spec.Bitmask as Bitmask import qualified Spec.Range as Range import qualified Spec.Run as Run diff --git a/tests/Spec/Bitmask.hs b/tests/Spec/Bitmask.hs deleted file mode 100644 index bd43505a0..000000000 --- a/tests/Spec/Bitmask.hs +++ /dev/null @@ -1,19 +0,0 @@ -module Spec.Bitmask (symmetric, bounded, singleton) where - -import Data.Bits -import System.Random - -symmetric :: (RandomGen g, FiniteBits a, Num a, Ord a, Random a) => g -> (a, a) -> Bool -symmetric g (l, r) = fst (bitmaskWithRejection (l, r) g) == fst (bitmaskWithRejection (r, l) g) - -bounded :: (RandomGen g, FiniteBits a, Num a, Ord a, Random a) => g -> (a, a) -> Bool -bounded g (l, r) = bottom <= result && result <= top - where - bottom = min l r - top = max l r - result = fst (bitmaskWithRejection (l, r) g) - -singleton :: (RandomGen g, FiniteBits a, Num a, Ord a, Random a) => g -> a -> Bool -singleton g x = result == x - where - result = fst (bitmaskWithRejection (x, x) g) From 8669a69767a679e2b5abfe22bddfefdf7d38ffc1 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 3 Apr 2020 11:17:02 +0200 Subject: [PATCH 077/170] Uncomment now-working tests --- tests/Legacy/RangeTest.hs | 64 +++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/tests/Legacy/RangeTest.hs b/tests/Legacy/RangeTest.hs index 6cb957181..bee03e12e 100644 --- a/tests/Legacy/RangeTest.hs +++ b/tests/Legacy/RangeTest.hs @@ -59,9 +59,8 @@ trials = 5000 main = do checkBounds "Int" boundedRange (approxBounds random trials (undefined::Int)) - -- FIXME Implement the relevant UniformRange instances and uncomment the tests below. - --checkBounds "Integer" (False, fromIntegral (minBound::Int), fromIntegral (maxBound::Int)) - -- (approxBounds random trials (undefined::Integer)) + checkBounds "Integer" (False, fromIntegral (minBound::Int), fromIntegral (maxBound::Int)) + (approxBounds random trials (undefined::Integer)) checkBounds "Int8" boundedRange (approxBounds random trials (undefined::Int8)) checkBounds "Int16" boundedRange (approxBounds random trials (undefined::Int16)) checkBounds "Int32" boundedRange (approxBounds random trials (undefined::Int32)) @@ -95,17 +94,16 @@ main = checkBounds "CUIntMax" boundedRange (approxBounds random trials (undefined:: CUIntMax)) -- Then check all the range-restricted versions: - -- FIXME Implement the relevant UniformRange instances and uncomment the tests below. - --checkBounds "Int R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int)) - --checkBounds "Integer R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Integer)) - --checkBounds "Int8 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int8)) - --checkBounds "Int8 Rsmall" (False,-50,50) (approxBounds (randomR (-50,50)) trials (undefined::Int8)) - --checkBounds "Int8 Rmini" (False,3,4) (approxBounds (randomR (3,4)) trials (undefined::Int8)) - --checkBounds "Int8 Rtrivial" (False,3,3) (approxBounds (randomR (3,3)) trials (undefined::Int8)) - - --checkBounds "Int16 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int16)) - --checkBounds "Int32 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int32)) - --checkBounds "Int64 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int64)) + checkBounds "Int R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int)) + checkBounds "Integer R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Integer)) + checkBounds "Int8 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int8)) + checkBounds "Int8 Rsmall" (False,-50,50) (approxBounds (randomR (-50,50)) trials (undefined::Int8)) + checkBounds "Int8 Rmini" (False,3,4) (approxBounds (randomR (3,4)) trials (undefined::Int8)) + checkBounds "Int8 Rtrivial" (False,3,3) (approxBounds (randomR (3,3)) trials (undefined::Int8)) + + checkBounds "Int16 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int16)) + checkBounds "Int32 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int32)) + checkBounds "Int64 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int64)) checkBounds "Word R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined::Word)) checkBounds "Word8 R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined::Word8)) checkBounds "Word16 R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined::Word16)) @@ -114,25 +112,25 @@ main = checkBounds "Double R" (True,10.0,77.0) (approxBounds (randomR (10,77)) trials (undefined::Double)) checkBounds "Float R" (True,10.0,77.0) (approxBounds (randomR (10,77)) trials (undefined::Float)) - --checkBounds "CChar R" (False,0,100) (approxBounds (randomR (0,100)) trials (undefined:: CChar)) - --checkBounds "CSChar R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CSChar)) - --checkBounds "CUChar R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUChar)) - --checkBounds "CShort R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CShort)) - --checkBounds "CUShort R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUShort)) - --checkBounds "CInt R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CInt)) - --checkBounds "CUInt R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUInt)) - --checkBounds "CLong R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CLong)) - --checkBounds "CULong R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CULong)) - --checkBounds "CPtrdiff R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CPtrdiff)) - --checkBounds "CSize R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CSize)) - --checkBounds "CWchar R" (False,0,100) (approxBounds (randomR (0,100)) trials (undefined:: CWchar)) - --checkBounds "CSigAtomic R" (False,0,100) (approxBounds (randomR (0,100)) trials (undefined:: CSigAtomic)) - --checkBounds "CLLong R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CLLong)) - --checkBounds "CULLong R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CULLong)) - --checkBounds "CIntPtr R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CIntPtr)) - --checkBounds "CUIntPtr R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUIntPtr)) - --checkBounds "CIntMax R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CIntMax)) - --checkBounds "CUIntMax R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUIntMax)) + checkBounds "CChar R" (False,0,100) (approxBounds (randomR (0,100)) trials (undefined:: CChar)) + checkBounds "CSChar R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CSChar)) + checkBounds "CUChar R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUChar)) + checkBounds "CShort R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CShort)) + checkBounds "CUShort R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUShort)) + checkBounds "CInt R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CInt)) + checkBounds "CUInt R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUInt)) + checkBounds "CLong R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CLong)) + checkBounds "CULong R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CULong)) + checkBounds "CPtrdiff R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CPtrdiff)) + checkBounds "CSize R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CSize)) + checkBounds "CWchar R" (False,0,100) (approxBounds (randomR (0,100)) trials (undefined:: CWchar)) + checkBounds "CSigAtomic R" (False,0,100) (approxBounds (randomR (0,100)) trials (undefined:: CSigAtomic)) + checkBounds "CLLong R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CLLong)) + checkBounds "CULLong R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CULLong)) + checkBounds "CIntPtr R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CIntPtr)) + checkBounds "CUIntPtr R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUIntPtr)) + checkBounds "CIntMax R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CIntMax)) + checkBounds "CUIntMax R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined:: CUIntMax)) -- Untested: -- instance Random Char where From a81d241cef12dbf12570839080feb98ed168a0ab Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 3 Apr 2020 11:25:01 +0200 Subject: [PATCH 078/170] Test more interesting Integer range --- tests/Legacy/RangeTest.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Legacy/RangeTest.hs b/tests/Legacy/RangeTest.hs index bee03e12e..7ed355591 100644 --- a/tests/Legacy/RangeTest.hs +++ b/tests/Legacy/RangeTest.hs @@ -95,7 +95,9 @@ main = -- Then check all the range-restricted versions: checkBounds "Int R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int)) - checkBounds "Integer R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Integer)) + checkBounds "Integer R" (False,-100000000000000000000,100000000000000000000) + (approxBounds (randomR (-100000000000000000000,100000000000000000000)) + trials (undefined::Integer)) checkBounds "Int8 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int8)) checkBounds "Int8 Rsmall" (False,-50,50) (approxBounds (randomR (-50,50)) trials (undefined::Int8)) checkBounds "Int8 Rmini" (False,3,4) (approxBounds (randomR (3,4)) trials (undefined::Int8)) From ce73692f749178177556bb9398137642b6f99748 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 3 Apr 2020 13:39:34 +0200 Subject: [PATCH 079/170] Resurrect legacy benchmarks --- Benchmark/Makefile | 24 ------------------- {Benchmark => bench-legacy}/BinSearch.hs | 0 {Benchmark => bench-legacy}/SimpleRNGBench.hs | 4 ++-- random.cabal | 14 +++++++++++ 4 files changed, 16 insertions(+), 26 deletions(-) delete mode 100644 Benchmark/Makefile rename {Benchmark => bench-legacy}/BinSearch.hs (100%) rename {Benchmark => bench-legacy}/SimpleRNGBench.hs (98%) diff --git a/Benchmark/Makefile b/Benchmark/Makefile deleted file mode 100644 index 8a84e6479..000000000 --- a/Benchmark/Makefile +++ /dev/null @@ -1,24 +0,0 @@ - - -#OPTS= -O2 -Wall -XCPP -OPTS= -O2 -Wall -XCPP -Werror - -all: lib bench - -lib: - (cd .. && ghc $(OPTS) --make System/Random.hs) - -bench: - ghc $(OPTS) -i.. --make SimpleRNGBench.hs - -# When benchmarking against other packages installed via cabal it is -# necessary to IGNORE the System/Random.hs files in the current directory. -# (Otherwise instances of RandomGen are confused.) -bench2: - ghc $(OPTS) -DTEST_COMPETITORS --make SimpleRNGBench.hs - -clean: - rm -f *.o *.hi SimpleRNGBench -# cabal clean -# (cd Benchmark/ && rm -f *.o *.hi SimpleRNGBench) -# (cd System/ && rm -f *.o *.hi SimpleRNGBench) diff --git a/Benchmark/BinSearch.hs b/bench-legacy/BinSearch.hs similarity index 100% rename from Benchmark/BinSearch.hs rename to bench-legacy/BinSearch.hs diff --git a/Benchmark/SimpleRNGBench.hs b/bench-legacy/SimpleRNGBench.hs similarity index 98% rename from Benchmark/SimpleRNGBench.hs rename to bench-legacy/SimpleRNGBench.hs index c25b75d47..a2f919d7d 100644 --- a/Benchmark/SimpleRNGBench.hs +++ b/bench-legacy/SimpleRNGBench.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE BangPatterns, ScopedTypeVariables, ForeignFunctionInterface #-} +{-# LANGUAGE BangPatterns, CPP, ScopedTypeVariables, ForeignFunctionInterface #-} {-# OPTIONS_GHC -fwarn-unused-imports #-} -- | A simple script to do some very basic timing of the RNGs. @@ -44,7 +44,7 @@ import GHC.IO -- Miscellaneous helpers: -- Readable large integer printing: -commaint :: Integral a => a -> String +commaint :: Show a => a -> String commaint n = reverse $ concat $ intersperse "," $ diff --git a/random.cabal b/random.cabal index 06536bd75..9d5533e67 100644 --- a/random.cabal +++ b/random.cabal @@ -84,3 +84,17 @@ test-suite spec tasty -any, tasty-smallcheck -any, tasty-expected-failure -any + +benchmark legacy-bench + type: exitcode-stdio-1.0 + main-is: SimpleRNGBench.hs + hs-source-dirs: bench-legacy + other-modules: BinSearch + default-language: Haskell2010 + ghc-options: -Wall -O2 -threaded -rtsopts -with-rtsopts=-N + build-depends: + base -any, + random -any, + rdtsc -any, + split -any, + time -any From d34570b695905f530d4a546540299ae8a7a60285 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 3 Apr 2020 13:52:57 +0200 Subject: [PATCH 080/170] Fix Coveralls CI build --- stack-coveralls.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stack-coveralls.yaml b/stack-coveralls.yaml index 948886305..5575c2ffb 100644 --- a/stack-coveralls.yaml +++ b/stack-coveralls.yaml @@ -1,7 +1,8 @@ resolver: lts-14.27 packages: - . -extra-deps: [] +extra-deps: +- rdtsc-1.3.0.1@sha256:0a6e8dc715ba82ad72c7e2b1c2f468999559bec059d50540719a80b00dcc4e66,1557 flags: splitmix: random: false From f2d52ef26ebf94344e66e025d7fd06974267ec19 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Thu, 2 Apr 2020 10:45:06 +0100 Subject: [PATCH 081/170] More efficient ranges for Word32 --- System/Random.hs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/System/Random.hs b/System/Random.hs index f2df903a3..e994f38fe 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -250,7 +250,7 @@ class RandomGen g where ((fromIntegral h32 `unsafeShiftL` 32) .|. fromIntegral l32, g'') genWord32R :: Word32 -> g -> (Word32, g) - genWord32R m g = runGenState g (unsignedBitmaskWithRejectionM uniformWord32 m) + genWord32R m g = runGenState g (unbiasedIntMult32 m) genWord64R :: Word64 -> g -> (Word64, g) genWord64R m g = runGenState g (unsignedBitmaskWithRejectionM uniformWord64 m) @@ -1089,6 +1089,18 @@ uniformIntegerM (l, h) gen let v' = v * b + fromIntegral x v' `seq` f (mag * b) v' +unbiasedIntMult32 :: MonadRandom g m => Word32 -> g -> m Word32 +unbiasedIntMult32 r g = go + where + t :: Word32 + t = (-r) `mod` r -- Calculates 2^32 `mod` r!!! + go = do + x <- uniformWord32 g + let m :: Word64 + m = (fromIntegral x) * (fromIntegral r) + l :: Word32 + l = fromIntegral m + if (l >= t) then return (fromIntegral $ m `shiftR` 32) else go -- | This only works for unsigned integrals unsignedBitmaskWithRejectionRM :: From 6d3c5c729d4a7e494713e5368510605765720428 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Thu, 2 Apr 2020 15:05:44 +0100 Subject: [PATCH 082/170] Fix coverage and tests --- System/Random.hs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index e994f38fe..d6bbc0331 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -789,7 +789,9 @@ instance Uniform Word32 where uniform = uniformWord32 instance UniformRange Word32 where {-# INLINE uniformR #-} - uniformR = unsignedBitmaskWithRejectionRM + uniformR (b, t) g | b == t = pure b + | b > t = unbiasedIntMult32 (b - t) g >>= return . (+t) + | otherwise = unbiasedIntMult32 (t - b) g >>= return . (+b) instance Random Word64 where randomM = uniform @@ -1090,8 +1092,12 @@ uniformIntegerM (l, h) gen v' `seq` f (mag * b) v' unbiasedIntMult32 :: MonadRandom g m => Word32 -> g -> m Word32 -unbiasedIntMult32 r g = go +unbiasedIntMult32 s g + | s == 0 = pure 0 + | otherwise = go where + r :: Word32 + r = s + 1 t :: Word32 t = (-r) `mod` r -- Calculates 2^32 `mod` r!!! go = do From b52ae9cd30e6d85cc94c75929a20121447e2fcbc Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Fri, 3 Apr 2020 08:03:07 +0100 Subject: [PATCH 083/170] Fix off by 1 --- System/Random.hs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index d6bbc0331..87bd63999 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -789,8 +789,7 @@ instance Uniform Word32 where uniform = uniformWord32 instance UniformRange Word32 where {-# INLINE uniformR #-} - uniformR (b, t) g | b == t = pure b - | b > t = unbiasedIntMult32 (b - t) g >>= return . (+t) + uniformR (b, t) g | b > t = unbiasedIntMult32 (b - t) g >>= return . (+t) | otherwise = unbiasedIntMult32 (t - b) g >>= return . (+b) instance Random Word64 where @@ -1093,7 +1092,7 @@ uniformIntegerM (l, h) gen unbiasedIntMult32 :: MonadRandom g m => Word32 -> g -> m Word32 unbiasedIntMult32 s g - | s == 0 = pure 0 + | s == maxBound = uniformWord32 g | otherwise = go where r :: Word32 From 9610530fdd380057c9770685d726f98821d37198 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Sun, 5 Apr 2020 17:12:56 +0100 Subject: [PATCH 084/170] References and stylistic changes --- System/Random.hs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 87bd63999..15ca7d6ff 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -250,7 +250,7 @@ class RandomGen g where ((fromIntegral h32 `unsafeShiftL` 32) .|. fromIntegral l32, g'') genWord32R :: Word32 -> g -> (Word32, g) - genWord32R m g = runGenState g (unbiasedIntMult32 m) + genWord32R m g = runGenState g (unbiasedWordMult32 m) genWord64R :: Word64 -> g -> (Word64, g) genWord64R m g = runGenState g (unsignedBitmaskWithRejectionM uniformWord64 m) @@ -789,8 +789,8 @@ instance Uniform Word32 where uniform = uniformWord32 instance UniformRange Word32 where {-# INLINE uniformR #-} - uniformR (b, t) g | b > t = unbiasedIntMult32 (b - t) g >>= return . (+t) - | otherwise = unbiasedIntMult32 (t - b) g >>= return . (+b) + uniformR (b, t) g | b > t = (+t) <$> unbiasedWordMult32 (b - t) g + | otherwise = (+b) <$> unbiasedWordMult32 (t - b) g instance Random Word64 where randomM = uniform @@ -1090,8 +1090,15 @@ uniformIntegerM (l, h) gen let v' = v * b + fromIntegral x v' `seq` f (mag * b) v' -unbiasedIntMult32 :: MonadRandom g m => Word32 -> g -> m Word32 -unbiasedIntMult32 s g +-- | A slightly modified version of what can be found in [Lemire's +-- paper](https://arxiv.org/pdf/1805.10941.pdf), [O'Neill's +-- blogpost](https://www.pcg-random.org/posts/bounded-rands.html) and +-- more directly from [O'Neill's github +-- repo](https://github.com/imneme/bounded-rands/blob/3d71f53c975b1e5b29f2f3b05a74e26dab9c3d84/bounded32.cpp#L234). +-- The modification is we want the range to be [0,t] not [0,t) to be +-- compatible with unsignedBitmaskWithRejectionRM. +unbiasedWordMult32 :: MonadRandom g m => Word32 -> g -> m Word32 +unbiasedWordMult32 s g | s == maxBound = uniformWord32 g | otherwise = go where From 126d5347c6288afda2b2c9fb7e827205070b0068 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Mon, 6 Apr 2020 09:44:38 +0100 Subject: [PATCH 085/170] Mirror code more exactly to the references --- System/Random.hs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 15ca7d6ff..77ba3d8f8 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1090,20 +1090,22 @@ uniformIntegerM (l, h) gen let v' = v * b + fromIntegral x v' `seq` f (mag * b) v' --- | A slightly modified version of what can be found in [Lemire's --- paper](https://arxiv.org/pdf/1805.10941.pdf), [O'Neill's --- blogpost](https://www.pcg-random.org/posts/bounded-rands.html) and --- more directly from [O'Neill's github --- repo](https://github.com/imneme/bounded-rands/blob/3d71f53c975b1e5b29f2f3b05a74e26dab9c3d84/bounded32.cpp#L234). --- The modification is we want the range to be [0,t] not [0,t) to be --- compatible with unsignedBitmaskWithRejectionRM. +-- | Uniformly generate Word32 in @[0, s]@. unbiasedWordMult32 :: MonadRandom g m => Word32 -> g -> m Word32 unbiasedWordMult32 s g | s == maxBound = uniformWord32 g - | otherwise = go + | otherwise = unbiasedWordMult32Exclusive (s+1) g +{-# INLINE unbiasedWordMult32 #-} + +-- | See [Lemire's paper](https://arxiv.org/pdf/1805.10941.pdf), +-- [O'Neill's +-- blogpost](https://www.pcg-random.org/posts/bounded-rands.html) and +-- more directly [O'Neill's github +-- repo](https://github.com/imneme/bounded-rands/blob/3d71f53c975b1e5b29f2f3b05a74e26dab9c3d84/bounded32.cpp#L234). +-- N.B. The range is [0,t) **not** [0,t]. +unbiasedWordMult32Exclusive :: MonadRandom g m => Word32 -> g -> m Word32 +unbiasedWordMult32Exclusive r g = go where - r :: Word32 - r = s + 1 t :: Word32 t = (-r) `mod` r -- Calculates 2^32 `mod` r!!! go = do From 3003c6038e26213dcdaa2ec50239c76fda13756c Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Mon, 30 Mar 2020 14:20:38 +0200 Subject: [PATCH 086/170] Port module-level documentation to Haddock --- System/Random.hs | 306 +++++++++++++++++++++++++++++++++++++------ random.cabal | 1 + stack-coveralls.yaml | 2 + stack-old.yaml | 7 +- stack.yaml | 4 +- 5 files changed, 279 insertions(+), 41 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index f2df903a3..b1797be79 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -20,39 +20,188 @@ -- Module : System.Random -- Copyright : (c) The University of Glasgow 2001 -- License : BSD-style (see the file LICENSE in the 'random' repository) --- -- Maintainer : libraries@haskell.org -- Stability : stable --- Portability : portable -- --- This library deals with the common task of pseudo-random number --- generation. The library makes it possible to generate repeatable --- results, by starting with a specified initial random number generator, --- or to get different results on each run by using the system-initialised --- generator or by supplying a seed from some other source. +-- This library deals with the common task of pseudo-random number generation. +-- +-- = Overview +-- This library provides type classes and instances for the following concepts: +-- +-- [Pure pseudo-random number generators] 'RandomGen' is an interface to pure +-- pseudo-random number generators. +-- +-- 'StdGen', the standard pseudo-random number generator provided in this +-- library, is an instance of 'RandomGen'. It uses the SplitMix +-- implementation provided by the +-- package. +-- Programmers may, of course, supply their own instances of 'RandomGen'. +-- +-- [Monadic pseudo-random number generators] 'MonadRandom' is an interface to +-- monadic pseudo-random number generators. +-- +-- [Monadic adapters] 'PureGen', 'PrimGen' and 'MutGen' turn a 'RandomGen' +-- instance into a 'MonadRandom' instance. +-- +-- [Drawing from a range] 'UniformRange' is used to generate a value of a +-- datatype uniformly within a range. +-- +-- This library provides instances of 'UniformRange' for many common +-- numeric datatypes. +-- +-- [Drawing from the entire domain of a type] 'Uniform' is used to generate a +-- value of a datatype uniformly over all possible values of that datatype. +-- +-- This library provides instances of 'Uniform' for many common bounded +-- numeric datatypes. +-- +-- = Backwards compatibility and deprecations +-- +-- Version 1.2 mostly maintains backwards compatibility with version 1.1. This +-- has a few consequences users should be aware of: +-- +-- * The type class 'Random' is deprecated and only provided for backwards +-- compatibility. New code should use 'Uniform' and 'UniformRange' instead. +-- +-- * The methods 'next' and 'genRange' in 'RandomGen' are deprecated and only +-- provided for backwards compatibility. New instances of 'RandomGen' should +-- implement word-based methods instead. See below for more information about +-- how to write a 'RandomGen' instance. +-- +-- * This library provides instances for 'Random' for some unbounded datatypes +-- for backwards compatibility. For an unbounded datatype, there is no way to +-- generate a value with uniform probability out of its entire domain, so the +-- 'random' implementation for unbounded datatypes actually generates a value +-- based on some fixed range. +-- +-- For 'Integer', 'random' generates a value in the 'Int' range. For 'Float' and 'Double', 'random' generates a floating point value in the range @[0, 1)@. +-- +-- This library does not provide 'Uniform' instances for any unbounded datatypes. -- --- The library is split into two layers: +-- = Reproducibility -- --- * A core /random number generator/ provides a supply of bits. --- The class 'RandomGen' provides a common interface to such generators. --- The library provides one instance of 'RandomGen', the abstract --- data type 'StdGen'. Programmers may, of course, supply their own --- instances of 'RandomGen'. +-- If you have two builds of a particular piece of code against this library, +-- any deterministic function call should give the same result in the two +-- builds if the builds are -- --- * The class 'Random' provides a way to extract values of a particular --- type from a random number generator. For example, the 'Float' --- instance of 'Random' allows one to generate random values of type --- 'Float'. +-- * compiled against the same major version of this library +-- * on the same architecture (32-bit or 64-bit) -- --- This implementation uses the SplitMix algorithm [1]. +-- = Pure and monadic pseudo-random number generators -- --- [/Example for RNG Implementors:/] +-- Pseudo-random number generators come in two flavours: /pure/ and /monadic/. +-- +-- [Pure pseudo-random number generators] These generators produce a new random +-- value together with a new instance of the pseudo-random number +-- generator. 'RandomGen' defines the interface for pure pseudo-random +-- number generators. +-- +-- Pure pseudo-random number generators should implement 'split' if they +-- are /splittable/, that is, if there is an efficient method to turn one +-- instance of the generator into two such that the pseudo-random numbers +-- produced by the two resulting generators are not correlated. See [1] for +-- some background on splittable pseudo-random generators. +-- +-- [Monadic pseudo-random number generators] These generators mutate their own +-- state as they produce random values. They generally live in 'ST' or 'IO' +-- or some transformer that implements 'PrimMonad'. 'MonadRandom' defines +-- the interface for monadic pseudo-random number generators. +-- +-- Pure pseudo-random number generators can be used in monadic code via the +-- adapters 'PureGen', 'PrimGen' and 'MutGen'. +-- +-- * 'PureGen' can be used in any state monad. With strict 'StateT' there is +-- even no performance overhead when compared to the 'RandomGen' directly. +-- But it is not safe to use it in presence of exceptions and resource +-- allocation that requires cleanup. +-- +-- * 'PrimGen' can be used in any 'PrimMonad' if the pseudo-random number +-- generator is also an instance of 'Prim'. It will perform much faster +-- than 'MutGen', but it can't be used in a concurrent setting. +-- +-- * 'MutGen' can be used in any 'PrimMonad' (this includes 'ST' and 'IO') and +-- can safely be shared between threads. +-- +-- When to use which? +-- +-- * Use 'PureGen' if your computation does not have side effects and results +-- in a pure function. +-- +-- * Use 'PrimGen' if the pseudo-random number generator implements 'Prim' +-- class and random value generation is intermixed with 'IO' or 'ST'. +-- +-- * Otherwise use 'MutGen'. Whenever a 'MutGen' is shared between threads, +-- make sure there is not much contention for it, otherwise performance +-- will suffer. For parallel random value generation it is best to split +-- the generator and use a single 'PureGen' or 'PrimGen' per thread. +-- +-- = How to generate random values in monadic code +-- +-- In monadic code, use the relevant 'Uniform' and 'UniformRange' instances to +-- generate random values via 'uniform' and 'uniformR', respectively. +-- +-- As an example, @rolls@ generates @n@ random values of @Word8@ in the range +-- @[1, 6]@. +-- +-- >>> :{ +-- let rolls :: MonadRandom g m => Int -> g -> m [Word8] +-- rolls n = replicateM n . uniformR (1, 6) +-- :} +-- +-- Given a /monadic/ pseudo-random number generator, you can run this +-- probabilistic computation as follows: +-- +-- >>> monadicGen <- MWC.create +-- >>> rolls 10 monadicGen :: IO [Word8] +-- [2,3,6,6,4,4,3,1,5,4] +-- +-- Given a /pure/ pseudo-random number generator, you can run it in an 'IO' or +-- 'ST' context using 'runPrimGenIO' or 'runPrimGenST' and their variants, +-- respectively. If the pseudo-random number generator does not implement +-- 'Prim', you can also use 'runMutGenIO' or 'runMutGenST' and their variants. +-- +-- >>> let pureGen = mkStdGen 42 +-- >>> runPrimGenIO_ pureGen (rolls 10) :: IO [Word8] +-- [1,1,3,2,4,5,3,4,6,2] +-- +-- = How to generate random values in pure code +-- +-- In pure code, use 'runGenState' and its variants to extract the pure random +-- value from a monadic computation based on a pure pseudo-random number +-- generator. +-- +-- >>> let pureGen = mkStdGen 42 +-- >>> runGenState_ pureGen (rolls 10) :: [Word8] +-- [1,1,3,2,4,5,3,4,6,2] +-- +-- = How to implement 'RandomGen' +-- +-- Consider these points when writing a 'RandomGen' instance for a given pure +-- pseudo-random number generator: +-- +-- * If the pseudo-random number generator has a power-of-2 modulus, that is, +-- it natively outputs @2^n@ bits of randomness for some @n@, implement +-- 'genWord8', 'genWord16', 'genWord32' and 'genWord64'. See below for more +-- details. +-- +-- * If the pseudo-random number generator does not have a power-of-2 +-- modulus, implement 'next' and 'genRange'. See below for more details. +-- +-- * If the pseudo-random number generator is splittable, implement 'split'. +-- +-- Additionally, implement 'Prim' for the pseudo-random number generator if +-- possible. This allows users to use the fast 'MutGen' adapter with the +-- pseudo-random number generator. +-- +-- == How to implement 'RandomGen' for a pseudo-random number generator with power-of-2 modulus -- -- Suppose you want to implement a [permuted congruential -- generator](https://en.wikipedia.org/wiki/Permuted_congruential_generator). -- -- >>> data PCGen = PCGen !Word64 !Word64 -- +-- It produces a full 'Word32' of randomness per iteration. +-- -- >>> :{ -- let stepGen :: PCGen -> (Word32, PCGen) -- stepGen (PCGen state inc) = let @@ -66,38 +215,91 @@ -- >>> fst $ stepGen $ snd $ stepGen (PCGen 17 29) -- 3288430965 -- --- Once implemented an instance of `RandomGen` can be created: +-- You can make it an instance of 'RandomGen' as follows: -- -- >>> :{ -- instance RandomGen PCGen where -- genWord32 = stepGen --- split _ = error "This PRNG is not splittable" +-- genWord64 g = (buildWord64 x y, g'') +-- where +-- (x, g') = stepGen g +-- (y, g'') = stepGen g' -- :} -- --- Note, that depending on thow many bits of randomness your RNG produces in one iteration --- you might need to implement a different function in the `RandomGen` class. +-- This definition satisfies the compiler. However, the default implementations +-- of 'genWord8' and 'genWord16' are geared towards backwards compatibility +-- with 'RandomGen' instances based on 'next' and 'genRange'. This means that +-- they are not optimal for pseudo-random number generators with a power-of-2 +-- modulo. -- --- Once implemented, every pure generator can be used with `MonadRandom` by the means of --- `PureGen` and state transformers. See an example below of such use case. +-- So let's implement a faster 'RandomGen' instance for our pseudo-random +-- number generator as follows: -- --- [/Example for RNG Users:/] +-- >>> newtype PCGen' = PCGen' { unPCGen :: PCGen } +-- >>> let stepGen' = second PCGen' . stepGen . unPCGen +-- >>> :{ +-- instance RandomGen PCGen' where +-- genWord8 = first fromIntegral . stepGen' +-- genWord16 = first fromIntegral . stepGen' +-- genWord32 = stepGen' +-- genWord64 g = (buildWord64 x y, g'') +-- where +-- (x, g') = stepGen' g +-- (y, g'') = stepGen' g' +-- :} -- --- Suppose you want to simulate a number of rolls from a dice (yes I know it's a --- plural form but it's now common to use it as a singular form): +-- == How to implement 'RandomGen' for a pseudo-random number generator without a power-of-2 modulus +-- +-- Suppose you want to implement the pseudo-random number generator from +-- . It +-- natively generates an integer value in the range @[1, 2147483562]@. This is +-- the generator used by this library before it was replaced by SplitMix in +-- version 1.2. +-- +-- >>> data LegacyGen = LegacyGen !Int32 !Int32 +-- >>> :{ +-- let legacyNext :: LegacyGen -> (Int, LegacyGen) +-- legacyNext (LegacyGen s1 s2) = (fromIntegral z', LegacyGen s1'' s2'') where +-- z' = if z < 1 then z + 2147483562 else z +-- z = s1'' - s2'' +-- k = s1 `quot` 53668 +-- s1' = 40014 * (s1 - k * 53668) - k * 12211 +-- s1'' = if s1' < 0 then s1' + 2147483563 else s1' +-- k' = s2 `quot` 52774 +-- s2' = 40692 * (s2 - k' * 52774) - k' * 3791 +-- s2'' = if s2' < 0 then s2' + 2147483399 else s2' +-- :} +-- +-- You can make it an instance of 'RandomGen' as follows: -- -- >>> :{ --- let rolls :: Int -> [Word] --- rolls n = runGenState_ --- (PCGen 17 29) --- (\g -> replicateM n (uniformR (1, 6) g)) +-- instance RandomGen LegacyGen where +-- next = legacyNext +-- genRange _ = (1, 2147483562) -- :} -- --- >>> rolls 20 --- [1,1,5,3,6,3,3,2,4,3,2,3,3,4,6,6,5,6,1,1] +-- Note that since the default implementations of all other 'RandomGen' methods +-- are geared towards pseudo-random number generators with 'next' as the source +-- of randomness for backwards compatibility, there is no need to implement +-- additional methods. You should of course verify this with a benchmark. +-- +-- = How to implement 'MonadRandom' -- --- FIXME: What should we say about generating values from types other --- than Word8 etc? +-- Typically, a monadic pseudo-random number generator has facilities to save +-- and restore its internal state in addition to generating random +-- pseudo-random numbers. -- +-- Here is an example instance for the monadic pseudo-random number generator +-- from the @mwc-random@ package: +-- +-- > instance (s ~ PrimState m, PrimMonad m) => MonadRandom (MWC.Gen s) m where +-- > newtype Frozen (MWC.Gen s) = FrozenGen MWC.Seed +-- > thawGen (FrozenGen s) = MWC.restore s +-- > freezeGen = fmap FrozenGen . MWC.save +-- > uniformWord8 = MWC.uniform +-- > uniformWord16 = MWC.uniform +-- > uniformWord32 = MWC.uniform +-- > uniformWord64 = MWC.uniform ----------------------------------------------------------------------------- module System.Random @@ -210,10 +412,35 @@ mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 -- $setup -- >>> import Control.Arrow (first, second) -- >>> import Control.Monad (replicateM) +-- >>> import Control.Monad.Primitive -- >>> import Data.Bits --- >>> import Data.Word +-- >>> import Data.Int (Int32) +-- >>> import Data.Word (Word8, Word16, Word32, Word64) +-- >>> import System.IO (IOMode(WriteMode), withBinaryFile) +-- >>> import qualified System.Random.MWC as MWC +-- -- >>> :set -XFlexibleContexts +-- >>> :set -XFlexibleInstances +-- >>> :set -XMultiParamTypeClasses +-- >>> :set -XTypeFamilies +-- -- >>> :set -fno-warn-missing-methods +-- +-- >>> :{ +-- let buildWord64 :: Word32 -> Word32 -> Word64 +-- buildWord64 x y = (fromIntegral x `shiftL` 32) .|. fromIntegral y +-- :} +-- +-- >>> :{ +-- instance (s ~ PrimState m, PrimMonad m) => MonadRandom (MWC.Gen s) m where +-- newtype Frozen (MWC.Gen s) = FrozenGen MWC.Seed +-- thawGen (FrozenGen s) = MWC.restore s +-- freezeGen = fmap FrozenGen . MWC.save +-- uniformWord8 = MWC.uniform +-- uniformWord16 = MWC.uniform +-- uniformWord32 = MWC.uniform +-- uniformWord64 = MWC.uniform +-- :} -- | The class 'RandomGen' provides a common interface to random number -- generators. @@ -289,10 +516,11 @@ class Monad m => MonadRandom g m where thawGen :: Frozen g -> m g freezeGen :: g -> m (Frozen g) - -- | Generate `Word32` up to and including the supplied max value + -- | Generate 'Word32' up to and including the supplied max value uniformWord32R :: Word32 -> g -> m Word32 uniformWord32R = unsignedBitmaskWithRejectionM uniformWord32 - -- | Generate `Word64` up to and including the supplied max value + + -- | Generate 'Word64' up to and including the supplied max value uniformWord64R :: Word64 -> g -> m Word64 uniformWord64R = unsignedBitmaskWithRejectionM uniformWord64 diff --git a/random.cabal b/random.cabal index 9d5533e67..36eb8be3b 100644 --- a/random.cabal +++ b/random.cabal @@ -65,6 +65,7 @@ test-suite doctests build-depends: base -any, doctest >=0.15, + mwc-random -any, random -any, unliftio -any diff --git a/stack-coveralls.yaml b/stack-coveralls.yaml index 5575c2ffb..0103119c5 100644 --- a/stack-coveralls.yaml +++ b/stack-coveralls.yaml @@ -3,6 +3,8 @@ packages: - . extra-deps: - rdtsc-1.3.0.1@sha256:0a6e8dc715ba82ad72c7e2b1c2f468999559bec059d50540719a80b00dcc4e66,1557 +- git: https://github.com/lehins/splitmix.git + commit: bb92e025a0d0a0353e2d6369191fecec8e8c99ad flags: splitmix: random: false diff --git a/stack-old.yaml b/stack-old.yaml index 119fc9b74..f689cdc66 100644 --- a/stack-old.yaml +++ b/stack-old.yaml @@ -46,7 +46,12 @@ extra-deps: # Note that although the resolver defined in this file comes with a splitmix # >= 0.0.3 by default, this stack.yaml is also used in CI, where the resolver # is overridden, making this explicit pinning of splitmix necessary. -- splitmix-0.0.4@sha256:fb9bb8b54a2e76c8a021fe5c4c3798047e1f60e168379a1f80693047fe00ad0e,4813 + # + # In addition, the doctests require 'SMGen' from the splitmix package to + # implement 'Prim'. So far, this is only the case on lehin's fork, so we use + # that. +- git: https://github.com/lehins/splitmix.git + commit: bb92e025a0d0a0353e2d6369191fecec8e8c99ad # Not contained in all snapshots we want to test against - doctest-0.16.2@sha256:2f96e9bbe9aee11b47453c82c24b3dc76cdbb8a2a7c984dfd60b4906d08adf68,6942 - cabal-doctest-1.0.8@sha256:34dff6369d417df2699af4e15f06bc181d495eca9c51efde173deae2053c197c,1491 diff --git a/stack.yaml b/stack.yaml index 572921a65..ca5be157b 100644 --- a/stack.yaml +++ b/stack.yaml @@ -34,7 +34,9 @@ packages: # Dependency packages to be pulled from upstream that are not in the resolver. # These entries can reference officially published versions as well as # forks / in-progress versions pinned to a git hash. For example: -extra-deps: [] +extra-deps: +- git: https://github.com/lehins/splitmix.git + commit: bb92e025a0d0a0353e2d6369191fecec8e8c99ad # Override default flag values for local packages and extra-deps flags: From eeda9fb0dda417bf1de6b65326524283bcf6fd15 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 3 Apr 2020 10:54:28 +0200 Subject: [PATCH 087/170] Talk about split --- System/Random.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/System/Random.hs b/System/Random.hs index b1797be79..c3785c476 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1410,7 +1410,7 @@ getStdRandom f = atomicModifyIORef' theStdGen (swap . f) pseudorandom number generators. In Proceedings of the 2014 ACM International Conference on Object Oriented Programming Systems Languages & Applications (OOPSLA '14). ACM, New York, NY, USA, 453-472. DOI: -https://doi.org/10.1145/2660193.2660195 + -} From 99f47a2083c97e8dea2229c82027f97ff6a22bce Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Mon, 6 Apr 2020 11:12:10 +0200 Subject: [PATCH 088/170] Clarify our stance towards non-power-of-2 PRNGs --- System/Random.hs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index c3785c476..50797a32e 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -250,11 +250,20 @@ -- -- == How to implement 'RandomGen' for a pseudo-random number generator without a power-of-2 modulus -- --- Suppose you want to implement the pseudo-random number generator from --- . It --- natively generates an integer value in the range @[1, 2147483562]@. This is --- the generator used by this library before it was replaced by SplitMix in --- version 1.2. +-- __We do not recommend you implement any new pseudo-random number generators without a power-of-2 modulus.__ +-- +-- Pseudo-random number generators without a power-of-2 modulus perform +-- /significantly worse/ than pseudo-random number generators with a power-of-2 +-- modulus with this library. This is because most functionality in this +-- library is based on generating and transforming uniformly random machine +-- words, and generating uniformly random machine words using a pseudo-random +-- number generator without a power-of-2 modulus is expensive. +-- +-- The pseudo-random number generator from +-- natively +-- generates an integer value in the range @[1, 2147483562]@. This is the +-- generator used by this library before it was replaced by SplitMix in version +-- 1.2. -- -- >>> data LegacyGen = LegacyGen !Int32 !Int32 -- >>> :{ @@ -278,11 +287,6 @@ -- genRange _ = (1, 2147483562) -- :} -- --- Note that since the default implementations of all other 'RandomGen' methods --- are geared towards pseudo-random number generators with 'next' as the source --- of randomness for backwards compatibility, there is no need to implement --- additional methods. You should of course verify this with a benchmark. --- -- = How to implement 'MonadRandom' -- -- Typically, a monadic pseudo-random number generator has facilities to save From 9c6dc92262f710ed9f8d683bf94a0e8262a15a22 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Mon, 6 Apr 2020 16:17:30 +0300 Subject: [PATCH 089/170] Improve performance of `Char` generation --- System/Random.hs | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 77ba3d8f8..3b6a325d8 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -749,8 +749,10 @@ instance Uniform Int where #else uniform = fmap (fromIntegral :: Word64 -> Int) . uniformWord64 #endif + {-# INLINE uniform #-} instance UniformRange Int where uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int -> Word) fromIntegral + {-# INLINE uniformR #-} instance Random Word where randomM = uniform @@ -956,16 +958,30 @@ instance UniformRange CDouble where uniformR (CDouble l, CDouble h) = fmap CDouble . uniformR (l, h) +-- The `chr#` and `ord#` are the prim functions that will be called, regardless of which +-- way you gonna do the `Char` conversion, so it is better to call them directly and +-- bypass all the hoops. Also because `intToChar` and `charToInt` are internal functions +-- and are called on valid character ranges it is impossible to generate an invalid +-- `Char`, therefore it is totally fine to omit all the unnecessary checks involved in +-- other paths of conversion. +word32ToChar :: Word32 -> Char +word32ToChar (W32# w#) = C# (chr# (word2Int# w#)) +{-# INLINE word32ToChar #-} + +charToWord32 :: Char -> Word32 +charToWord32 (C# c#) = W32# (int2Word# (ord# c#)) +{-# INLINE charToWord32 #-} + instance Random Char where randomM = uniform + {-# INLINE randomM #-} instance Uniform Char where - uniform = uniformR (minBound, maxBound) + uniform g = word32ToChar <$> unsignedBitmaskWithRejectionM uniform (charToWord32 maxBound) g + {-# INLINE uniform #-} instance UniformRange Char where - uniformR (l, h) g = toChar <$> unsignedBitmaskWithRejectionRM (fromChar l, fromChar h) g - where - fromChar (C# c#) = W# (int2Word# (ord# c#)) - toChar (W# w#) = C# (chr# (word2Int# w#)) - + uniformR (l, h) g = + word32ToChar <$> unsignedBitmaskWithRejectionRM (charToWord32 l, charToWord32 h) g + {-# INLINE uniformR #-} instance Random Bool where randomM = uniform From 98b1b3d6f3e7a6b7fb9c7755a9c765ce93ff89ac Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Mon, 6 Apr 2020 13:01:26 +0200 Subject: [PATCH 090/170] Faster 'uniform' and 'uniformR' for Bool Benchmark: How many random numbers can we generate in a second on one thread? First, timing System.Random.next: BEFORE AFTER Second, timing System.Random.random at different types: 1,859,311 randoms generated [System.Random Bools] ~ 932 cycles/int ~ 22.32 cycles/int Next timing range-restricted System.Random.randomR: 1,608,582 randoms generated [System.Random Bools] ~ 1,077 cycles/int ~ 22.46 cycles/int Now 8 threads, reporting mean randoms-per-second-per-thread: First, timing System.Random.next: Second, timing System.Random.random at different types: 726,606 randoms generated [System.Random Bools] ~ 2,385 cycles/int ~ 43.49 cycles/int Next timing range-restricted System.Random.randomR: 687,065 randoms generated [System.Random Bools] ~ 2,523 cycles/int ~ 46.32 cycles/int --- System/Random.hs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 77ba3d8f8..2e9cb1989 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -970,16 +970,12 @@ instance UniformRange Char where instance Random Bool where randomM = uniform instance Uniform Bool where - uniform = uniformR (minBound, maxBound) + uniform = fmap wordToBool . uniformWord8 + where wordToBool w = (w .&. 1) /= 0 instance UniformRange Bool where - uniformR (a, b) g = int2Bool <$> uniformR (bool2Int a, bool2Int b) g - where - bool2Int :: Bool -> Int - bool2Int False = 0 - bool2Int True = 1 - int2Bool :: Int -> Bool - int2Bool 0 = False - int2Bool _ = True + uniformR (False, False) _g = return False + uniformR (True, True) _g = return True + uniformR _ g = uniform g instance Random Double where randomR r g = runGenState g (uniformR r) From 49947c3bd8b390d4ca745de33ada0710f58ca9cd Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 2 Apr 2020 13:22:23 +0200 Subject: [PATCH 091/170] Unbiased uniformR for Integers --- System/Random.hs | 82 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 15 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index c61cbc35d..3d4fe6768 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} @@ -1083,24 +1084,75 @@ randomIvalInteger (l,h) rng (x,g') = next g v' = (v * b + (fromIntegral x - fromIntegral genlo)) +-- | Generate an 'Integer' in the range @[l, h]@ if @l <= h@ and @[h, l]@ +-- otherwise. uniformIntegerM :: (MonadRandom g m) => (Integer, Integer) -> g -> m Integer -uniformIntegerM (l, h) gen - | l > h = uniformIntegerM (h, l) gen - | otherwise = do - v <- f 1 0 - pure (l + v `mod` k) +uniformIntegerM (l, h) gen = case l `compare` h of + LT -> do + let limit = h - l + let limitAsWord64 :: Word64 = fromIntegral limit + bounded <- + if (toInteger limitAsWord64) == limit + -- Optimisation: if 'limit' fits into 'Word64', generate a bounded + -- 'Word64' and then convert to 'Integer' + then toInteger <$> unsignedBitmaskWithRejectionM uniformWord64 limitAsWord64 gen + else boundedExclusiveIntegerM (limit + 1) gen + return $ l + bounded + GT -> uniformIntegerM (h, l) gen + EQ -> pure l +{-# INLINE uniformIntegerM #-} + +-- | Generate an 'Integer' in the range @[0, s)@ using a variant of Lemire's +-- multiplication method. +-- +-- Daniel Lemire. 2019. Fast Random Integer Generation in an Interval. In ACM +-- Transactions on Modeling and Computer Simulation +-- https://doi.org/10.1145/3230636 +-- +-- PRECONDITION (unchecked): s > 0 +boundedExclusiveIntegerM :: (MonadRandom g m) => Integer -> g -> m Integer +boundedExclusiveIntegerM s gen = go + where + n = integerWordSize s + -- We renamed 'L' from the paper to 'k' here because 'L' is not a valid + -- variable name in Haskell and 'l' is already used in the algorithm. + k = WORD_SIZE_IN_BITS * n + twoToK = (1::Integer) `shiftL` k + modTwoToKMask = twoToK - 1 + + t = (twoToK - s) `mod` s + go = do + x <- uniformIntegerWords n gen + let m = x * s + -- m .&. modTwoToKMask == m `mod` twoToK + let l = m .&. modTwoToKMask + if l < t + then go + -- m `shiftR` k == m `quot` twoToK + else return $ m `shiftR` k +{-# INLINE boundedExclusiveIntegerM #-} + +-- | @integerWordSize i@ returns that least @w@ such that +-- @i <= WORD_SIZE_IN_BITS^w@. +integerWordSize :: Integer -> Int +integerWordSize = go 0 + where + go !acc i + | i == 0 = acc + | otherwise = go (acc + 1) (i `shiftR` WORD_SIZE_IN_BITS) +{-# INLINE integerWordSize #-} + +-- | @uniformIntegerWords n@ is a uniformly random 'Integer' in the range +-- @[0, WORD_SIZE_IN_BITS^n)@. +uniformIntegerWords :: (MonadRandom g m) => Int -> g -> m Integer +uniformIntegerWords n gen = go 0 n where - b = toInteger (maxBound :: Word64) - q = 1000 - k = h - l + 1 - magtgt = k * q - -- generate random values until we exceed the target magnitude - f mag v - | mag >= magtgt = pure v + go !acc i + | i == 0 = return acc | otherwise = do - x <- uniformWord64 gen - let v' = v * b + fromIntegral x - v' `seq` f (mag * b) v' + (w :: Word) <- uniform gen + go ((acc `shiftL` WORD_SIZE_IN_BITS) .|. (fromIntegral w)) (i - 1) +{-# INLINE uniformIntegerWords #-} -- | Uniformly generate Word32 in @[0, s]@. unbiasedWordMult32 :: MonadRandom g m => Word32 -> g -> m Word32 From 274d5166131a6f59f86d6f996a81b4b547e09028 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Mon, 6 Apr 2020 09:46:48 +0200 Subject: [PATCH 092/170] Random Integer instance: use Int functions --- System/Random.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 3d4fe6768..eacbfef27 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -706,10 +706,10 @@ buildRandoms cons rand = go -- The seq fixes part of #4218 and also makes fused Core simpler. go g = x `seq` (x `cons` go g') where (x,g') = rand g - +-- Generate values in the Int range instance Random Integer where - random g = randomR (toInteger (minBound::Int), toInteger (maxBound::Int)) g - randomM g = uniformR (toInteger (minBound::Int), toInteger (maxBound::Int)) g + random = first (toInteger :: Int -> Integer) . random + randomM = fmap (toInteger :: Int -> Integer) . randomM instance UniformRange Integer where uniformR = uniformIntegerM From 8a1ceb298561ed9c19a298615f979bc63b01ddb2 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Tue, 7 Apr 2020 16:13:43 +0100 Subject: [PATCH 093/170] Start of changelog entry --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15c882af9..85893cf7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,3 +24,17 @@ bump for bug fixes, # 1.0.0.4 bumped version for float/double range bugfix +# 1.2 + + * Breaking change which mostly maintains backwards compatibility. + * Default generator of [splitmix](https://hackage.haskell.org/package/splitmix). + * Faster by more x10 (depending on the type). + * Passes a large number of random number test suites: dieharder, + TestU01 (SmallCrush, Crush, BigCrush), PractRand, gjrand. See + [random-quality](https://github.com/tweag/random-quality) for + details on how to do this yourself. + * Better quality split as judged by these [tests](https://www.cambridge.org/core/journals/journal-of-functional-programming/article/evaluation-of-splittable-pseudorandom-generators/3EBAA9F14939C5BB5560E32D1A132637) + + + + From 6cd137fb0bcc6c58ece5e489f0193043f21f1518 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Tue, 7 Apr 2020 16:15:25 +0100 Subject: [PATCH 094/170] Revert "Start of changelog entry" This reverts commit 8a1ceb298561ed9c19a298615f979bc63b01ddb2. --- CHANGELOG.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85893cf7a..15c882af9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,17 +24,3 @@ bump for bug fixes, # 1.0.0.4 bumped version for float/double range bugfix -# 1.2 - - * Breaking change which mostly maintains backwards compatibility. - * Default generator of [splitmix](https://hackage.haskell.org/package/splitmix). - * Faster by more x10 (depending on the type). - * Passes a large number of random number test suites: dieharder, - TestU01 (SmallCrush, Crush, BigCrush), PractRand, gjrand. See - [random-quality](https://github.com/tweag/random-quality) for - details on how to do this yourself. - * Better quality split as judged by these [tests](https://www.cambridge.org/core/journals/journal-of-functional-programming/article/evaluation-of-splittable-pseudorandom-generators/3EBAA9F14939C5BB5560E32D1A132637) - - - - From b83bebd8ada8156f41d12532c2b73c89e3a695a1 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Wed, 8 Apr 2020 03:03:56 +0300 Subject: [PATCH 095/170] Addition of state token to MonadRandom (rebased) --- System/Random.hs | 137 ++++++++++++++++++++++++----------------------- 1 file changed, 69 insertions(+), 68 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 4db944c79..01d50c404 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -3,12 +3,14 @@ {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE FunctionalDependencies #-} +{-# LANGUAGE GHCForeignImportPrim #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE GHCForeignImportPrim #-} +{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UnliftedFFITypes #-} #if __GLASGOW_HASKELL__ >= 701 {-# LANGUAGE Trustworthy #-} @@ -145,7 +147,7 @@ -- @[1, 6]@. -- -- >>> :{ --- let rolls :: MonadRandom g m => Int -> g -> m [Word8] +-- let rolls :: MonadRandom g s m => Int -> g s -> m [Word8] -- rolls n = replicateM n . uniformR (1, 6) -- :} -- @@ -297,10 +299,10 @@ -- Here is an example instance for the monadic pseudo-random number generator -- from the @mwc-random@ package: -- --- > instance (s ~ PrimState m, PrimMonad m) => MonadRandom (MWC.Gen s) m where --- > newtype Frozen (MWC.Gen s) = FrozenGen MWC.Seed --- > thawGen (FrozenGen s) = MWC.restore s --- > freezeGen = fmap FrozenGen . MWC.save +-- > instance (s ~ PrimState m, PrimMonad m) => MonadRandom MWC.Gen s m where +-- > newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } +-- > thawGen = fmap MWC.restore unFrozen +-- > freezeGen = fmap Frozen . MWC.save -- > uniformWord8 = MWC.uniform -- > uniformWord16 = MWC.uniform -- > uniformWord32 = MWC.uniform @@ -428,6 +430,7 @@ mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 -- >>> :set -XFlexibleInstances -- >>> :set -XMultiParamTypeClasses -- >>> :set -XTypeFamilies +-- >>> :set -XUndecidableInstances -- -- >>> :set -fno-warn-missing-methods -- @@ -437,10 +440,10 @@ mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 -- :} -- -- >>> :{ --- instance (s ~ PrimState m, PrimMonad m) => MonadRandom (MWC.Gen s) m where --- newtype Frozen (MWC.Gen s) = FrozenGen MWC.Seed --- thawGen (FrozenGen s) = MWC.restore s --- freezeGen = fmap FrozenGen . MWC.save +-- instance (s ~ PrimState m, PrimMonad m) => MonadRandom MWC.Gen s m where +-- newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } +-- thawGen = fmap MWC.restore unFrozen +-- freezeGen = fmap Frozen . MWC.save -- uniformWord8 = MWC.uniform -- uniformWord16 = MWC.uniform -- uniformWord32 = MWC.uniform @@ -515,51 +518,51 @@ class RandomGen g where -- generators. split :: g -> (g, g) -class Monad m => MonadRandom g m where +class Monad m => MonadRandom (g :: * -> *) s m | m -> s where data Frozen g :: * {-# MINIMAL freezeGen,thawGen,(uniformWord32|uniformWord64) #-} - thawGen :: Frozen g -> m g - freezeGen :: g -> m (Frozen g) + thawGen :: Frozen g -> m (g s) + freezeGen :: g s -> m (Frozen g) -- | Generate 'Word32' up to and including the supplied max value - uniformWord32R :: Word32 -> g -> m Word32 + uniformWord32R :: Word32 -> g s -> m Word32 uniformWord32R = unsignedBitmaskWithRejectionM uniformWord32 -- | Generate 'Word64' up to and including the supplied max value - uniformWord64R :: Word64 -> g -> m Word64 + uniformWord64R :: Word64 -> g s -> m Word64 uniformWord64R = unsignedBitmaskWithRejectionM uniformWord64 - uniformWord8 :: g -> m Word8 + uniformWord8 :: g s -> m Word8 uniformWord8 = fmap fromIntegral . uniformWord32 - uniformWord16 :: g -> m Word16 + uniformWord16 :: g s -> m Word16 uniformWord16 = fmap fromIntegral . uniformWord32 - uniformWord32 :: g -> m Word32 + uniformWord32 :: g s -> m Word32 uniformWord32 = fmap fromIntegral . uniformWord64 - uniformWord64 :: g -> m Word64 + uniformWord64 :: g s -> m Word64 uniformWord64 g = do l32 <- uniformWord32 g h32 <- uniformWord32 g pure (unsafeShiftL (fromIntegral h32) 32 .|. fromIntegral l32) - uniformByteArray :: Int -> g -> m ByteArray - default uniformByteArray :: PrimMonad m => Int -> g -> m ByteArray + uniformByteArray :: Int -> g s -> m ByteArray + default uniformByteArray :: PrimMonad m => Int -> g s -> m ByteArray uniformByteArray = uniformByteArrayPrim {-# INLINE uniformByteArray #-} -withGenM :: MonadRandom g m => Frozen g -> (g -> m a) -> m (a, Frozen g) +withGenM :: MonadRandom g s m => Frozen g -> (g s -> m a) -> m (a, Frozen g) withGenM fg action = do g <- thawGen fg res <- action g fg' <- freezeGen g pure (res, fg') -uniformListM :: (MonadRandom g m, Uniform a) => g -> Int -> m [a] +uniformListM :: (MonadRandom g s m, Uniform a) => g s -> Int -> m [a] uniformListM gen n = replicateM n (uniform gen) -- | This function will efficiently generate a sequence of random bytes in a platform -- independent manner. Memory allocated will be pinned, so it is safe to use for FFI -- calls. -uniformByteArrayPrim :: (MonadRandom g m, PrimMonad m) => Int -> g -> m ByteArray +uniformByteArrayPrim :: (MonadRandom g s m, PrimMonad m) => Int -> g s -> m ByteArray uniformByteArrayPrim n0 gen = do let n = max 0 n0 (n64, nrem64) = n `quotRem` 8 @@ -606,7 +609,7 @@ pinnedMutableByteArrayToForeignPtr mba@(MutableByteArray mba#) = -- -- @since 1.2 uniformByteStringPrim :: - (MonadRandom g m, PrimMonad m) => Int -> g -> m ByteString + (MonadRandom g s m, PrimMonad m) => Int -> g s -> m ByteString uniformByteStringPrim n g = do ba@(ByteArray ba#) <- uniformByteArray n g if isByteArrayPinned ba @@ -626,15 +629,15 @@ genByteString n g = runPureGenST g (uniformByteStringPrim n) -- | Run an effectful generating action in `ST` monad using a pure generator. -- -- @since 1.2 -runPureGenST :: RandomGen g => g -> (forall s . PureGen g -> StateT g (ST s) a) -> (a, g) +runPureGenST :: RandomGen g => g -> (forall s . PureGen g g -> StateT g (ST s) a) -> (a, g) runPureGenST g action = runST $ runGenStateT g $ action {-# INLINE runPureGenST #-} -- | An opaque data type that carries the type of a pure generator -data PureGen g = PureGenI +data PureGen g s = PureGenI -instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) m where +instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) g m where newtype Frozen (PureGen g) = PureGen g thawGen (PureGen g) = PureGenI <$ put g freezeGen _ =fmap PureGen get @@ -649,7 +652,7 @@ instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) m where -- | Generate a random value in a state monad -- -- @since 1.2 -genRandom :: (RandomGen g, Random a, MonadState g m) => PureGen g -> m a +genRandom :: (RandomGen g, Random a, MonadState g m) => PureGen g g -> m a genRandom = randomM -- | Split current generator and update the state with one part, while returning the other. @@ -658,16 +661,16 @@ genRandom = randomM splitGen :: (MonadState g m, RandomGen g) => m g splitGen = state split -runGenState :: RandomGen g => g -> (PureGen g -> State g a) -> (a, g) +runGenState :: RandomGen g => g -> (PureGen g g -> State g a) -> (a, g) runGenState g f = runState (f PureGenI) g -runGenState_ :: RandomGen g => g -> (PureGen g -> State g a) -> a +runGenState_ :: RandomGen g => g -> (PureGen g g -> State g a) -> a runGenState_ g = fst . runGenState g -runGenStateT :: RandomGen g => g -> (PureGen g -> StateT g m a) -> m (a, g) +runGenStateT :: RandomGen g => g -> (PureGen g g -> StateT g m a) -> m (a, g) runGenStateT g f = runStateT (f PureGenI) g -runGenStateT_ :: (RandomGen g, Functor f) => g -> (PureGen g -> StateT g f a) -> f a +runGenStateT_ :: (RandomGen g, Functor f) => g -> (PureGen g g -> StateT g f a) -> f a runGenStateT_ g = fmap fst . runGenStateT g -- | This is a wrapper wround pure generator that can be used in an effectful environment. @@ -675,11 +678,11 @@ runGenStateT_ g = fmap fst . runGenStateT g -- atomically. -- -- @since 1.2 -newtype MutGen s g = MutGenI (MutVar s g) +newtype MutGen g s = MutGenI (MutVar s g) instance (s ~ PrimState m, PrimMonad m, RandomGen g) => - MonadRandom (MutGen s g) m where - newtype Frozen (MutGen s g) = MutGen g + MonadRandom (MutGen g) s m where + newtype Frozen (MutGen g) = MutGen g thawGen (MutGen g) = fmap MutGenI (newMutVar g) freezeGen (MutGenI gVar) = fmap MutGen (readMutVar gVar) uniformWord32R r = atomicMutGen (genWord32R r) @@ -692,7 +695,7 @@ instance (s ~ PrimState m, PrimMonad m, RandomGen g) => uniformByteArray n = atomicMutGen (genByteArray n) -- | Apply a pure operation to generator atomically. -atomicMutGen :: PrimMonad m => (g -> (a, g)) -> MutGen (PrimState m) g -> m a +atomicMutGen :: PrimMonad m => (g -> (a, g)) -> MutGen g (PrimState m) -> m a atomicMutGen op (MutGenI gVar) = atomicModifyMutVar' gVar $ \g -> case op g of @@ -705,12 +708,10 @@ atomicMutGen op (MutGenI gVar) = -- -- @since 1.2 splitMutGen :: - (RandomGen g, PrimMonad m) - => MutGen (PrimState m) g - -> m (MutGen (PrimState m) g) + (RandomGen g, PrimMonad m, s ~ PrimState m) => MutGen g s -> m (MutGen g s) splitMutGen = atomicMutGen split >=> thawGen . MutGen -runMutGenST :: RandomGen g => g -> (forall s . MutGen s g -> ST s a) -> (a, g) +runMutGenST :: RandomGen g => g -> (forall s . MutGen g s -> ST s a) -> (a, g) runMutGenST g action = runST $ do mutGen <- thawGen $ MutGen g res <- action mutGen @@ -718,7 +719,7 @@ runMutGenST g action = runST $ do pure (res, g') -- | Same as `runMutGenST`, but discard the resulting generator. -runMutGenST_ :: RandomGen g => g -> (forall s . MutGen s g -> ST s a) -> a +runMutGenST_ :: RandomGen g => g -> (forall s . MutGen g s -> ST s a) -> a runMutGenST_ g action = fst $ runMutGenST g action -- | Both `PrimGen` and `MutGen` and their corresponding functions like 'runPrimGenIO' are @@ -734,7 +735,7 @@ runMutGenST_ g action = fst $ runMutGenST g action -- -- >>> runMutGenIO_ (mkStdGen 1729) ioGen -- -runMutGenIO :: (RandomGen g, MonadIO m) => g -> (MutGen RealWorld g -> m a) -> m (a, g) +runMutGenIO :: (RandomGen g, MonadIO m) => g -> (MutGen g RealWorld -> m a) -> m (a, g) runMutGenIO g action = do mutGen <- liftIO $ thawGen $ MutGen g res <- action mutGen @@ -743,16 +744,16 @@ runMutGenIO g action = do {-# INLINE runMutGenIO #-} -- | Same as `runMutGenIO`, but discard the resulting generator. -runMutGenIO_ :: (RandomGen g, MonadIO m) => g -> (MutGen RealWorld g -> m a) -> m a +runMutGenIO_ :: (RandomGen g, MonadIO m) => g -> (MutGen g RealWorld -> m a) -> m a runMutGenIO_ g action = fst <$> runMutGenIO g action {-# INLINE runMutGenIO_ #-} -newtype PrimGen s g = PrimGenI (MutableByteArray s) +newtype PrimGen g s = PrimGenI (MutableByteArray s) instance (s ~ PrimState m, PrimMonad m, RandomGen g, Prim g) => - MonadRandom (PrimGen s g) m where - newtype Frozen (PrimGen s g) = PrimGen g + MonadRandom (PrimGen g) s m where + newtype Frozen (PrimGen g) = PrimGen g thawGen (PrimGen g) = do ma <- newByteArray (Primitive.sizeOf g) writeByteArray ma 0 g @@ -766,7 +767,7 @@ instance (s ~ PrimState m, PrimMonad m, RandomGen g, Prim g) => uniformWord64 = applyPrimGen genWord64 uniformByteArray n = applyPrimGen (genByteArray n) -applyPrimGen :: (Prim g, PrimMonad m) => (g -> (a, g)) -> PrimGen (PrimState m) g -> m a +applyPrimGen :: (Prim g, PrimMonad m, s ~ PrimState m) => (g -> (a, g)) -> PrimGen g s -> m a applyPrimGen f (PrimGenI ma) = do g <- readByteArray ma 0 case f g of @@ -777,12 +778,12 @@ applyPrimGen f (PrimGenI ma) = do -- -- @since 1.2 splitPrimGen :: - (Prim g, RandomGen g, PrimMonad m) - => PrimGen (PrimState m) g - -> m (PrimGen (PrimState m) g) + (Prim g, RandomGen g, PrimMonad m, s ~ PrimState m) + => PrimGen g s + -> m (PrimGen g s) splitPrimGen = applyPrimGen split >=> thawGen . PrimGen -runPrimGenST :: (Prim g, RandomGen g) => g -> (forall s . PrimGen s g -> ST s a) -> (a, g) +runPrimGenST :: (Prim g, RandomGen g) => g -> (forall s . PrimGen g s -> ST s a) -> (a, g) runPrimGenST g action = runST $ do primGen <- thawGen $ PrimGen g res <- action primGen @@ -790,10 +791,10 @@ runPrimGenST g action = runST $ do pure (res, g') -- | Same as `runPrimGenST`, but discard the resulting generator. -runPrimGenST_ :: (Prim g, RandomGen g) => g -> (forall s . PrimGen s g -> ST s a) -> a +runPrimGenST_ :: (Prim g, RandomGen g) => g -> (forall s . PrimGen g s -> ST s a) -> a runPrimGenST_ g action = fst $ runPrimGenST g action -runPrimGenIO :: (Prim g, RandomGen g, MonadIO m) => g -> (PrimGen RealWorld g -> m a) -> m (a, g) +runPrimGenIO :: (Prim g, RandomGen g, MonadIO m) => g -> (PrimGen g RealWorld -> m a) -> m (a, g) runPrimGenIO g action = do primGen <- liftIO $ thawGen $ PrimGen g res <- action primGen @@ -801,7 +802,7 @@ runPrimGenIO g action = do pure (res, g') -- | Same as `runPrimGenIO`, but discard the resulting generator. -runPrimGenIO_ :: (Prim g, RandomGen g, MonadIO m) => g -> (PrimGen RealWorld g -> m a) -> m a +runPrimGenIO_ :: (Prim g, RandomGen g, MonadIO m) => g -> (PrimGen g RealWorld -> m a) -> m a runPrimGenIO_ g action = fst <$> runPrimGenIO g action type StdGen = SM.SMGen @@ -849,7 +850,7 @@ mkStdGen s = SM.mkSMGen $ fromIntegral s -- | Generate every possible value for data type with equal probability. class Uniform a where - uniform :: MonadRandom g m => g -> m a + uniform :: MonadRandom g s m => g s -> m a -- | Generate every value in provided inclusive range with equal -- probability. So @uniformR (1,4)@ should generate values from set @@ -861,7 +862,7 @@ class Uniform a where -- -- > uniformR (a,b) = uniform (b,a) class UniformRange a where - uniformR :: MonadRandom g m => (a, a) -> g -> m a + uniformR :: MonadRandom g s m => (a, a) -> g s -> m a {- | @@ -900,7 +901,7 @@ class Random a where random g = runGenState g genRandom --{-# INLINE randomM #-} - randomM :: MonadRandom g m => g -> m a + randomM :: MonadRandom g s m => g s -> m a -- default randomM :: (MonadRandom g m, Uniform a) => g -> m a -- randomM = uniform @@ -1318,7 +1319,7 @@ randomIvalInteger (l,h) rng -- | Generate an 'Integer' in the range @[l, h]@ if @l <= h@ and @[h, l]@ -- otherwise. -uniformIntegerM :: (MonadRandom g m) => (Integer, Integer) -> g -> m Integer +uniformIntegerM :: (MonadRandom g s m) => (Integer, Integer) -> g s -> m Integer uniformIntegerM (l, h) gen = case l `compare` h of LT -> do let limit = h - l @@ -1342,7 +1343,7 @@ uniformIntegerM (l, h) gen = case l `compare` h of -- https://doi.org/10.1145/3230636 -- -- PRECONDITION (unchecked): s > 0 -boundedExclusiveIntegerM :: (MonadRandom g m) => Integer -> g -> m Integer +boundedExclusiveIntegerM :: (MonadRandom g s m) => Integer -> g s -> m Integer boundedExclusiveIntegerM s gen = go where n = integerWordSize s @@ -1376,7 +1377,7 @@ integerWordSize = go 0 -- | @uniformIntegerWords n@ is a uniformly random 'Integer' in the range -- @[0, WORD_SIZE_IN_BITS^n)@. -uniformIntegerWords :: (MonadRandom g m) => Int -> g -> m Integer +uniformIntegerWords :: (MonadRandom g s m) => Int -> g s -> m Integer uniformIntegerWords n gen = go 0 n where go !acc i @@ -1387,7 +1388,7 @@ uniformIntegerWords n gen = go 0 n {-# INLINE uniformIntegerWords #-} -- | Uniformly generate Word32 in @[0, s]@. -unbiasedWordMult32 :: MonadRandom g m => Word32 -> g -> m Word32 +unbiasedWordMult32 :: MonadRandom g s m => Word32 -> g s -> m Word32 unbiasedWordMult32 s g | s == maxBound = uniformWord32 g | otherwise = unbiasedWordMult32Exclusive (s+1) g @@ -1399,7 +1400,7 @@ unbiasedWordMult32 s g -- more directly [O'Neill's github -- repo](https://github.com/imneme/bounded-rands/blob/3d71f53c975b1e5b29f2f3b05a74e26dab9c3d84/bounded32.cpp#L234). -- N.B. The range is [0,t) **not** [0,t]. -unbiasedWordMult32Exclusive :: MonadRandom g m => Word32 -> g -> m Word32 +unbiasedWordMult32Exclusive :: MonadRandom g s m => Word32 -> g s -> m Word32 unbiasedWordMult32Exclusive r g = go where t :: Word32 @@ -1414,9 +1415,9 @@ unbiasedWordMult32Exclusive r g = go -- | This only works for unsigned integrals unsignedBitmaskWithRejectionRM :: - (MonadRandom g m, FiniteBits a, Num a, Ord a, Uniform a) + (MonadRandom g s m, FiniteBits a, Num a, Ord a, Uniform a) => (a, a) - -> g + -> g s -> m a unsignedBitmaskWithRejectionRM (bottom, top) gen | bottom > top = unsignedBitmaskWithRejectionRM (top, bottom) gen @@ -1428,11 +1429,11 @@ unsignedBitmaskWithRejectionRM (bottom, top) gen -- | This works for signed integrals by explicit conversion to unsigned and abusing overflow signedBitmaskWithRejectionRM :: - (Num a, Num b, Ord b, Ord a, FiniteBits a, MonadRandom g f, Uniform a) + (Num a, Num b, Ord b, Ord a, FiniteBits a, MonadRandom g s f, Uniform a) => (b -> a) -> (a -> b) -> (b, b) - -> g + -> g s -> f b signedBitmaskWithRejectionRM toUnsigned fromUnsigned (bottom, top) gen | bottom > top = signedBitmaskWithRejectionRM toUnsigned fromUnsigned (top, bottom) gen @@ -1444,7 +1445,7 @@ signedBitmaskWithRejectionRM toUnsigned fromUnsigned (bottom, top) gen range = toUnsigned top - toUnsigned bottom {-# INLINE signedBitmaskWithRejectionRM #-} -unsignedBitmaskWithRejectionM :: (Ord a, FiniteBits a, Num a, MonadRandom g m) => (g -> m a) -> a -> g -> m a +unsignedBitmaskWithRejectionM :: (Ord a, FiniteBits a, Num a, MonadRandom g s m) => (g s -> m a) -> a -> g s -> m a unsignedBitmaskWithRejectionM genUniform range gen = go where mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) From f53dee2284b85a1fef23bae52e93c531d292eecc Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Wed, 8 Apr 2020 04:22:08 +0300 Subject: [PATCH 096/170] Addition of IOGen, STGen and AtomicGen --- System/Random.hs | 462 ++++++++++++++++++++++++++-------------------- random.cabal | 1 - tests/Spec/Run.hs | 8 +- 3 files changed, 266 insertions(+), 205 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 01d50c404..3aa78d36f 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -3,18 +3,16 @@ {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GHCForeignImportPrim #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE Trustworthy #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE UnboxedTuples #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UnliftedFFITypes #-} -#if __GLASGOW_HASKELL__ >= 701 -{-# LANGUAGE Trustworthy #-} -#endif #include "MachDeps.h" @@ -334,23 +332,24 @@ module System.Random , runGenStateT , runGenStateT_ , runPureGenST - -- ** Based on PrimMonad - -- *** MutGen - boxed thread safe state - , MutGen - , runMutGenST - , runMutGenST_ - , runMutGenIO - , runMutGenIO_ - , splitMutGen - , atomicMutGen - -- *** PrimGen - unboxed mutable state - , PrimGen - , runPrimGenST - , runPrimGenST_ - , runPrimGenIO - , runPrimGenIO_ - , splitPrimGen - , applyPrimGen + -- ** Mutable generators + -- *** AtomicGen - + , AtomicGen + -- , runAtomicGenST + -- , runAtomicGenST_ + -- , runAtomicGenIO + -- , runAtomicGenIO_ + -- , splitAtomicGen + -- , atomicAtomicGen + , STGen + -- -- *** PrimGen - unboxed mutable state + -- , PrimGen + -- , runPrimGenST + -- , runPrimGenST_ + -- , runPrimGenIO + -- , runPrimGenIO_ + -- , splitPrimGen + -- , applyPrimGen -- ** The global random number generator @@ -369,8 +368,8 @@ module System.Random , Random(..) -- * Generators for sequences of bytes - , uniformByteArrayPrim - , uniformByteStringPrim + , genShortByteStringWith + , uniformByteString , genByteString -- * References @@ -379,7 +378,6 @@ module System.Random import Control.Arrow import Control.Monad.IO.Class -import Control.Monad.Primitive import Control.Monad.ST import Control.Monad.State.Strict import Data.Bits @@ -388,10 +386,8 @@ import Data.ByteString.Builder.Prim.Internal (runF) import Data.ByteString.Internal (ByteString(PS)) import Data.ByteString.Short.Internal (ShortByteString(SBS), fromShort) import Data.Int -import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef, writeIORef) -import Data.Primitive.ByteArray -import Data.Primitive.MutVar -import Data.Primitive.Types as Primitive (Prim, sizeOf) +import Data.IORef +import Data.STRef import Data.Word import Foreign.C.Types import Foreign.Marshal.Alloc (alloca) @@ -402,19 +398,7 @@ import GHC.ForeignPtr import System.IO.Unsafe (unsafePerformIO) import qualified System.Random.SplitMix as SM import GHC.Word - - -#if !MIN_VERSION_primitive(0,7,0) -import Data.Primitive.Types (Addr(..)) - -mutableByteArrayContentsCompat mba = - case mutableByteArrayContents mba of - Addr addr# -> Ptr addr# -#else -mutableByteArrayContentsCompat = mutableByteArrayContents -#endif -mutableByteArrayContentsCompat :: MutableByteArray s -> Ptr Word8 -{-# INLINE mutableByteArrayContentsCompat #-} +import GHC.IO (IO(..)) -- $setup -- >>> import Control.Arrow (first, second) @@ -490,10 +474,10 @@ class RandomGen g where genWord64R :: Word64 -> g -> (Word64, g) genWord64R m g = runGenState g (unsignedBitmaskWithRejectionM uniformWord64 m) - genByteArray :: Int -> g -> (ByteArray, g) - genByteArray n g = runPureGenST g $ uniformByteArrayPrim n - - {-# INLINE genByteArray #-} + genShortByteString :: Int -> g -> (ShortByteString, g) + genShortByteString n g = + unsafePerformIO $ runGenStateT g (genShortByteStringWith n . uniformWord64) + {-# INLINE genShortByteString #-} -- |The 'genRange' operation yields the range of values returned by -- the generator. -- @@ -518,7 +502,7 @@ class RandomGen g where -- generators. split :: g -> (g, g) -class Monad m => MonadRandom (g :: * -> *) s m | m -> s where +class Monad m => MonadRandom (g :: * -> *) s m where data Frozen g :: * {-# MINIMAL freezeGen,thawGen,(uniformWord32|uniformWord64) #-} @@ -543,10 +527,10 @@ class Monad m => MonadRandom (g :: * -> *) s m | m -> s where l32 <- uniformWord32 g h32 <- uniformWord32 g pure (unsafeShiftL (fromIntegral h32) 32 .|. fromIntegral l32) - uniformByteArray :: Int -> g s -> m ByteArray - default uniformByteArray :: PrimMonad m => Int -> g s -> m ByteArray - uniformByteArray = uniformByteArrayPrim - {-# INLINE uniformByteArray #-} + uniformShortByteString :: Int -> g s -> m ShortByteString + default uniformShortByteString :: MonadIO m => Int -> g s -> m ShortByteString + uniformShortByteString n = genShortByteStringWith n . uniformWord64 + {-# INLINE uniformShortByteString #-} withGenM :: MonadRandom g s m => Frozen g -> (g s -> m a) -> m (a, Frozen g) @@ -559,71 +543,78 @@ withGenM fg action = do uniformListM :: (MonadRandom g s m, Uniform a) => g s -> Int -> m [a] uniformListM gen n = replicateM n (uniform gen) +data MBA s = MBA (MutableByteArray# s) + + -- | This function will efficiently generate a sequence of random bytes in a platform --- independent manner. Memory allocated will be pinned, so it is safe to use for FFI +-- independent manner. Memory allocated will be pinned, so it is safe to use with FFI -- calls. -uniformByteArrayPrim :: (MonadRandom g s m, PrimMonad m) => Int -> g s -> m ByteArray -uniformByteArrayPrim n0 gen = do - let n = max 0 n0 +genShortByteStringWith :: MonadIO m => Int -> m Word64 -> m ShortByteString +genShortByteStringWith n0 gen64 = do + let !n@(I# n#) = max 0 n0 (n64, nrem64) = n `quotRem` 8 - ma <- newPinnedByteArray n + MBA mba# <- + liftIO $ + IO $ \s# -> + case newPinnedByteArray# n# s# of + (# s'#, mba# #) -> (# s'#, MBA mba# #) let go i ptr | i < n64 = do - w64 <- uniformWord64 gen + w64 <- gen64 -- Writing 8 bytes at a time in a Little-endian order gives us platform -- portability - unsafeIOToPrim $ runF word64LE w64 ptr + liftIO $ runF word64LE w64 ptr go (i + 1) (ptr `plusPtr` 8) | otherwise = return ptr - ptr <- go 0 (mutableByteArrayContentsCompat ma) + ptr <- go 0 (Ptr (byteArrayContents# (unsafeCoerce# mba#))) when (nrem64 > 0) $ do - w64 <- uniformWord64 gen + w64 <- gen64 -- In order to not mess up the byte order we write generated Word64 into a temporary -- pointer and then copy only the missing bytes over to the array. It is tempting to -- simply generate as many bytes as we still need using smaller generators -- (eg. uniformWord8), but that would result in inconsistent tail when total length is -- slightly varied. - unsafeIOToPrim $ + liftIO $ alloca $ \w64ptr -> do runF word64LE w64 w64ptr forM_ [0 .. nrem64 - 1] $ \i -> do w8 :: Word8 <- peekByteOff w64ptr i pokeByteOff ptr i w8 - unsafeFreezeByteArray ma -{-# INLINE uniformByteArrayPrim #-} + liftIO $ + IO $ \s# -> + case unsafeFreezeByteArray# mba# s# of + (# s'#, ba# #) -> (# s'#, SBS ba# #) +{-# INLINE genShortByteStringWith #-} -pinnedMutableByteArrayToByteString :: MutableByteArray RealWorld -> ByteString -pinnedMutableByteArrayToByteString mba = - PS (pinnedMutableByteArrayToForeignPtr mba) 0 (sizeofMutableByteArray mba) -{-# INLINE pinnedMutableByteArrayToByteString #-} +pinnedByteArrayToByteString :: ByteArray# -> ByteString +pinnedByteArrayToByteString ba# = + PS (pinnedByteArrayToForeignPtr ba#) 0 (I# (sizeofByteArray# ba#)) +{-# INLINE pinnedByteArrayToByteString #-} -pinnedMutableByteArrayToForeignPtr :: MutableByteArray RealWorld -> ForeignPtr a -pinnedMutableByteArrayToForeignPtr mba@(MutableByteArray mba#) = - case mutableByteArrayContentsCompat mba of - Ptr addr# -> ForeignPtr addr# (PlainPtr mba#) -{-# INLINE pinnedMutableByteArrayToForeignPtr #-} +pinnedByteArrayToForeignPtr :: ByteArray# -> ForeignPtr a +pinnedByteArrayToForeignPtr ba# = + ForeignPtr (byteArrayContents# ba#) (PlainPtr (unsafeCoerce# ba#)) +{-# INLINE pinnedByteArrayToForeignPtr #-} --- | Generate a ByteString using a pure generator. For monadic counterpart see --- `uniformByteStringPrim`. +-- | Generate a random ByteString of specified size. -- -- @since 1.2 -uniformByteStringPrim :: - (MonadRandom g s m, PrimMonad m) => Int -> g s -> m ByteString -uniformByteStringPrim n g = do - ba@(ByteArray ba#) <- uniformByteArray n g - if isByteArrayPinned ba - then unsafeIOToPrim $ - pinnedMutableByteArrayToByteString <$> unsafeThawByteArray ba - else return $ fromShort (SBS ba#) -{-# INLINE uniformByteStringPrim #-} +uniformByteString :: MonadRandom g s m => Int -> g s -> m ByteString +uniformByteString n g = do + ba@(SBS ba#) <- uniformShortByteString n g + pure $ + if isTrue# (isByteArrayPinned# ba#) + then pinnedByteArrayToByteString ba# + else fromShort ba +{-# INLINE uniformByteString #-} -- | Generate a ByteString using a pure generator. For monadic counterpart see -- `uniformByteStringPrim`. -- -- @since 1.2 genByteString :: RandomGen g => Int -> g -> (ByteString, g) -genByteString n g = runPureGenST g (uniformByteStringPrim n) +genByteString n g = runPureGenST g (uniformByteString n) {-# INLINE genByteString #-} -- | Run an effectful generating action in `ST` monad using a pure generator. @@ -640,14 +631,14 @@ data PureGen g s = PureGenI instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) g m where newtype Frozen (PureGen g) = PureGen g thawGen (PureGen g) = PureGenI <$ put g - freezeGen _ =fmap PureGen get + freezeGen _ = fmap PureGen get uniformWord32R r _ = state (genWord32R r) uniformWord64R r _ = state (genWord64R r) uniformWord8 _ = state genWord8 uniformWord16 _ = state genWord16 uniformWord32 _ = state genWord32 uniformWord64 _ = state genWord64 - uniformByteArray n _ = state (genByteArray n) + uniformShortByteString n _ = state (genShortByteString n) -- | Generate a random value in a state monad -- @@ -673,137 +664,208 @@ runGenStateT g f = runStateT (f PureGenI) g runGenStateT_ :: (RandomGen g, Functor f) => g -> (PureGen g g -> StateT g f a) -> f a runGenStateT_ g = fmap fst . runGenStateT g --- | This is a wrapper wround pure generator that can be used in an effectful environment. + +-- | This is a wrapper around pure generator that can be used in an effectful environment. -- It is safe in presence of exceptions and concurrency since all operations are performed -- atomically. -- -- @since 1.2 -newtype MutGen g s = MutGenI (MutVar s g) - -instance (s ~ PrimState m, PrimMonad m, RandomGen g) => - MonadRandom (MutGen g) s m where - newtype Frozen (MutGen g) = MutGen g - thawGen (MutGen g) = fmap MutGenI (newMutVar g) - freezeGen (MutGenI gVar) = fmap MutGen (readMutVar gVar) - uniformWord32R r = atomicMutGen (genWord32R r) - uniformWord64R r = atomicMutGen (genWord64R r) - uniformWord8 = atomicMutGen genWord8 - uniformWord16 = atomicMutGen genWord16 - uniformWord32 = atomicMutGen genWord32 - uniformWord64 = atomicMutGen genWord64 +newtype AtomicGen g s = AtomicGenI (IORef g) + +instance (MonadIO m, RandomGen g) => MonadRandom (AtomicGen g) g m where + newtype Frozen (AtomicGen g) = AtomicGen g + thawGen (AtomicGen g) = fmap AtomicGenI (liftIO $ newIORef g) + freezeGen (AtomicGenI gVar) = fmap AtomicGen (liftIO $ readIORef gVar) + uniformWord32R r = applyAtomicGen (genWord32R r) + {-# INLINE uniformWord32R #-} + uniformWord64R r = applyAtomicGen (genWord64R r) + {-# INLINE uniformWord64R #-} + uniformWord8 = applyAtomicGen genWord8 + {-# INLINE uniformWord8 #-} + uniformWord16 = applyAtomicGen genWord16 + {-# INLINE uniformWord16 #-} + uniformWord32 = applyAtomicGen genWord32 + {-# INLINE uniformWord32 #-} + uniformWord64 = applyAtomicGen genWord64 {-# INLINE uniformWord64 #-} - uniformByteArray n = atomicMutGen (genByteArray n) + uniformShortByteString n = applyAtomicGen (genShortByteString n) -- | Apply a pure operation to generator atomically. -atomicMutGen :: PrimMonad m => (g -> (a, g)) -> MutGen g (PrimState m) -> m a -atomicMutGen op (MutGenI gVar) = - atomicModifyMutVar' gVar $ \g -> +applyAtomicGen :: MonadIO m => (g -> (a, g)) -> AtomicGen g g -> m a +applyAtomicGen op (AtomicGenI gVar) = + liftIO $ atomicModifyIORef' gVar $ \g -> case op g of (a, g') -> (g', a) -{-# INLINE atomicMutGen #-} - +{-# INLINE applyAtomicGen #-} --- | Split `MutGen` into atomically updated current generator and a newly created that is --- returned. +-- | This is a wrapper wround an @IORef@ that holds a pure generator. Because of extra pointer +-- indirection it will be slightly slower than if `PureGen` is being used, but faster then +-- `AtomicGen` wrapper, since atomic modification is not being used with `IOGen`. Which also +-- means that it is not safe in a concurrent setting. -- -- @since 1.2 -splitMutGen :: - (RandomGen g, PrimMonad m, s ~ PrimState m) => MutGen g s -> m (MutGen g s) -splitMutGen = atomicMutGen split >=> thawGen . MutGen - -runMutGenST :: RandomGen g => g -> (forall s . MutGen g s -> ST s a) -> (a, g) -runMutGenST g action = runST $ do - mutGen <- thawGen $ MutGen g - res <- action mutGen - MutGen g' <- freezeGen mutGen - pure (res, g') - --- | Same as `runMutGenST`, but discard the resulting generator. -runMutGenST_ :: RandomGen g => g -> (forall s . MutGen g s -> ST s a) -> a -runMutGenST_ g action = fst $ runMutGenST g action - --- | Both `PrimGen` and `MutGen` and their corresponding functions like 'runPrimGenIO' are --- necessary when generation of random values happens in `IO` and especially when dealing --- with exception handling and resource allocation, which is where `StateT` should never be --- used. For example writing a random number of bytes into a temporary file: --- --- >>> import UnliftIO.Temporary (withSystemTempFile) --- >>> import Data.ByteString (hPutStr) --- >>> let ioGen g = withSystemTempFile "foo.bin" $ \_ h -> uniformR (0, 100) g >>= flip uniformByteStringPrim g >>= hPutStr h --- --- and then run it: --- --- >>> runMutGenIO_ (mkStdGen 1729) ioGen --- -runMutGenIO :: (RandomGen g, MonadIO m) => g -> (MutGen g RealWorld -> m a) -> m (a, g) -runMutGenIO g action = do - mutGen <- liftIO $ thawGen $ MutGen g - res <- action mutGen - MutGen g' <- liftIO $ freezeGen mutGen - pure (res, g') -{-# INLINE runMutGenIO #-} - --- | Same as `runMutGenIO`, but discard the resulting generator. -runMutGenIO_ :: (RandomGen g, MonadIO m) => g -> (MutGen g RealWorld -> m a) -> m a -runMutGenIO_ g action = fst <$> runMutGenIO g action -{-# INLINE runMutGenIO_ #-} - - -newtype PrimGen g s = PrimGenI (MutableByteArray s) - -instance (s ~ PrimState m, PrimMonad m, RandomGen g, Prim g) => - MonadRandom (PrimGen g) s m where - newtype Frozen (PrimGen g) = PrimGen g - thawGen (PrimGen g) = do - ma <- newByteArray (Primitive.sizeOf g) - writeByteArray ma 0 g - pure $ PrimGenI ma - freezeGen (PrimGenI ma) = PrimGen <$> readByteArray ma 0 - uniformWord32R r = applyPrimGen (genWord32R r) - uniformWord64R r = applyPrimGen (genWord64R r) - uniformWord8 = applyPrimGen genWord8 - uniformWord16 = applyPrimGen genWord16 - uniformWord32 = applyPrimGen genWord32 - uniformWord64 = applyPrimGen genWord64 - uniformByteArray n = applyPrimGen (genByteArray n) - -applyPrimGen :: (Prim g, PrimMonad m, s ~ PrimState m) => (g -> (a, g)) -> PrimGen g s -> m a -applyPrimGen f (PrimGenI ma) = do - g <- readByteArray ma 0 +newtype IOGen g s = IOGenI (IORef g) + +instance (RandomGen g, MonadIO m) => MonadRandom (IOGen g) g m where + newtype Frozen (IOGen g) = IOGen g + thawGen (IOGen g) = fmap IOGenI (liftIO $ newIORef g) + freezeGen (IOGenI gVar) = fmap IOGen (liftIO $ readIORef gVar) + uniformWord32R r = applyIOGen (genWord32R r) + {-# INLINE uniformWord32R #-} + uniformWord64R r = applyIOGen (genWord64R r) + {-# INLINE uniformWord64R #-} + uniformWord8 = applyIOGen genWord8 + {-# INLINE uniformWord8 #-} + uniformWord16 = applyIOGen genWord16 + {-# INLINE uniformWord16 #-} + uniformWord32 = applyIOGen genWord32 + {-# INLINE uniformWord32 #-} + uniformWord64 = applyIOGen genWord64 + {-# INLINE uniformWord64 #-} + uniformShortByteString n = applyIOGen (genShortByteString n) + +-- | Apply a pure operation to generator atomically. +applyIOGen :: MonadIO m => (g -> (a, g)) -> IOGen g g -> m a +applyIOGen f (IOGenI ref) = liftIO $ do + g <- readIORef ref case f g of - (res, g') -> res <$ writeByteArray ma 0 g' + (!a, !g') -> a <$ writeIORef ref g' +{-# INLINE applyIOGen #-} --- | Split `PrimGen` into atomically updated current generator and a newly created that is --- returned. + +-- | This is a wrapper wround an @STRef@ that holds a pure generator. Because of extra pointer +-- indirection it will be slightly slower than if `PureGen` is being used. -- -- @since 1.2 -splitPrimGen :: - (Prim g, RandomGen g, PrimMonad m, s ~ PrimState m) - => PrimGen g s - -> m (PrimGen g s) -splitPrimGen = applyPrimGen split >=> thawGen . PrimGen - -runPrimGenST :: (Prim g, RandomGen g) => g -> (forall s . PrimGen g s -> ST s a) -> (a, g) -runPrimGenST g action = runST $ do - primGen <- thawGen $ PrimGen g - res <- action primGen - PrimGen g' <- freezeGen primGen - pure (res, g') - --- | Same as `runPrimGenST`, but discard the resulting generator. -runPrimGenST_ :: (Prim g, RandomGen g) => g -> (forall s . PrimGen g s -> ST s a) -> a -runPrimGenST_ g action = fst $ runPrimGenST g action - -runPrimGenIO :: (Prim g, RandomGen g, MonadIO m) => g -> (PrimGen g RealWorld -> m a) -> m (a, g) -runPrimGenIO g action = do - primGen <- liftIO $ thawGen $ PrimGen g - res <- action primGen - PrimGen g' <- liftIO $ freezeGen primGen - pure (res, g') - --- | Same as `runPrimGenIO`, but discard the resulting generator. -runPrimGenIO_ :: (Prim g, RandomGen g, MonadIO m) => g -> (PrimGen g RealWorld -> m a) -> m a -runPrimGenIO_ g action = fst <$> runPrimGenIO g action +newtype STGen g s = STGenI (STRef s g) + +instance RandomGen g => MonadRandom (STGen g) s (ST s) where + newtype Frozen (STGen g) = STGen g + thawGen (STGen g) = fmap STGenI (newSTRef g) + freezeGen (STGenI gVar) = fmap STGen (readSTRef gVar) + uniformWord32R r = applySTGen (genWord32R r) + {-# INLINE uniformWord32R #-} + uniformWord64R r = applySTGen (genWord64R r) + {-# INLINE uniformWord64R #-} + uniformWord8 = applySTGen genWord8 + {-# INLINE uniformWord8 #-} + uniformWord16 = applySTGen genWord16 + {-# INLINE uniformWord16 #-} + uniformWord32 = applySTGen genWord32 + {-# INLINE uniformWord32 #-} + uniformWord64 = applySTGen genWord64 + {-# INLINE uniformWord64 #-} + uniformShortByteString n = applySTGen (genShortByteString n) + +-- | Apply a pure operation to generator atomically. +applySTGen :: (g -> (a, g)) -> STGen g s -> ST s a +applySTGen f (STGenI ref) = do + g <- readSTRef ref + case f g of + (!a, !g') -> a <$ writeSTRef ref g' +{-# INLINE applySTGen #-} + +-- -- | Split `MutGen` into atomically updated current generator and a newly created that is +-- -- returned. +-- -- +-- -- @since 1.2 +-- splitMutGen :: +-- (RandomGen g, PrimMonad m, s ~ PrimState m) => MutGen g s -> m (MutGen g s) +-- splitMutGen = atomicMutGen split >=> thawGen . MutGen + +-- runMutGenST :: RandomGen g => g -> (forall s . MutGen g s -> ST s a) -> (a, g) +-- runMutGenST g action = runST $ do +-- mutGen <- thawGen $ MutGen g +-- res <- action mutGen +-- MutGen g' <- freezeGen mutGen +-- pure (res, g') + +-- -- | Same as `runMutGenST`, but discard the resulting generator. +-- runMutGenST_ :: RandomGen g => g -> (forall s . MutGen g s -> ST s a) -> a +-- runMutGenST_ g action = fst $ runMutGenST g action + +-- -- | Both `PrimGen` and `MutGen` and their corresponding functions like 'runPrimGenIO' are +-- -- necessary when generation of random values happens in `IO` and especially when dealing +-- -- with exception handling and resource allocation, which is where `StateT` should never be +-- -- used. For example writing a random number of bytes into a temporary file: +-- -- +-- -- >>> import UnliftIO.Temporary (withSystemTempFile) +-- -- >>> import Data.ByteString (hPutStr) +-- -- >>> let ioGen g = withSystemTempFile "foo.bin" $ \_ h -> uniformR (0, 100) g >>= flip uniformByteStringPrim g >>= hPutStr h +-- -- +-- -- and then run it: +-- -- +-- -- >>> runMutGenIO_ (mkStdGen 1729) ioGen +-- -- +-- runMutGenIO :: (RandomGen g, MonadIO m) => g -> (MutGen g RealWorld -> m a) -> m (a, g) +-- runMutGenIO g action = do +-- mutGen <- liftIO $ thawGen $ MutGen g +-- res <- action mutGen +-- MutGen g' <- liftIO $ freezeGen mutGen +-- pure (res, g') +-- {-# INLINE runMutGenIO #-} + +-- -- | Same as `runMutGenIO`, but discard the resulting generator. +-- runMutGenIO_ :: (RandomGen g, MonadIO m) => g -> (MutGen g RealWorld -> m a) -> m a +-- runMutGenIO_ g action = fst <$> runMutGenIO g action +-- {-# INLINE runMutGenIO_ #-} + + +-- newtype PrimGen g s = PrimGenI (MutableByteArray s) + +-- instance (s ~ PrimState m, PrimMonad m, RandomGen g, Prim g) => +-- MonadRandom (PrimGen g) s m where +-- newtype Frozen (PrimGen g) = PrimGen g +-- thawGen (PrimGen g) = do +-- ma <- newByteArray (Primitive.sizeOf g) +-- writeByteArray ma 0 g +-- pure $ PrimGenI ma +-- freezeGen (PrimGenI ma) = PrimGen <$> readByteArray ma 0 +-- uniformWord32R r = applyPrimGen (genWord32R r) +-- uniformWord64R r = applyPrimGen (genWord64R r) +-- uniformWord8 = applyPrimGen genWord8 +-- uniformWord16 = applyPrimGen genWord16 +-- uniformWord32 = applyPrimGen genWord32 +-- uniformWord64 = applyPrimGen genWord64 +-- uniformByteArray n = applyPrimGen (genByteArray n) + +-- applyPrimGen :: (Prim g, PrimMonad m, s ~ PrimState m) => (g -> (a, g)) -> PrimGen g s -> m a +-- applyPrimGen f (PrimGenI ma) = do +-- g <- readByteArray ma 0 +-- case f g of +-- (res, g') -> res <$ writeByteArray ma 0 g' + +-- -- | Split `PrimGen` into atomically updated current generator and a newly created that is +-- -- returned. +-- -- +-- -- @since 1.2 +-- splitPrimGen :: +-- (Prim g, RandomGen g, PrimMonad m, s ~ PrimState m) +-- => PrimGen g s +-- -> m (PrimGen g s) +-- splitPrimGen = applyPrimGen split >=> thawGen . PrimGen + +-- runPrimGenST :: (Prim g, RandomGen g) => g -> (forall s . PrimGen g s -> ST s a) -> (a, g) +-- runPrimGenST g action = runST $ do +-- primGen <- thawGen $ PrimGen g +-- res <- action primGen +-- PrimGen g' <- freezeGen primGen +-- pure (res, g') + +-- -- | Same as `runPrimGenST`, but discard the resulting generator. +-- runPrimGenST_ :: (Prim g, RandomGen g) => g -> (forall s . PrimGen g s -> ST s a) -> a +-- runPrimGenST_ g action = fst $ runPrimGenST g action + +-- runPrimGenIO :: (Prim g, RandomGen g, MonadIO m) => g -> (PrimGen g RealWorld -> m a) -> m (a, g) +-- runPrimGenIO g action = do +-- primGen <- liftIO $ thawGen $ PrimGen g +-- res <- action primGen +-- PrimGen g' <- liftIO $ freezeGen primGen +-- pure (res, g') + +-- -- | Same as `runPrimGenIO`, but discard the resulting generator. +-- runPrimGenIO_ :: (Prim g, RandomGen g, MonadIO m) => g -> (PrimGen g RealWorld -> m a) -> m a +-- runPrimGenIO_ g action = fst <$> runPrimGenIO g action type StdGen = SM.SMGen diff --git a/random.cabal b/random.cabal index 36eb8be3b..244d49784 100644 --- a/random.cabal +++ b/random.cabal @@ -34,7 +34,6 @@ library build-depends: base >=4.10 && <5, bytestring -any, - primitive >= 0.6.4.0 && <8, mtl -any, splitmix -any c-sources: cbits/CastFloatWord.cmm diff --git a/tests/Spec/Run.hs b/tests/Spec/Run.hs index e18a38398..2566224bf 100644 --- a/tests/Spec/Run.hs +++ b/tests/Spec/Run.hs @@ -6,7 +6,7 @@ import Data.Word (Word64) import System.Random runsEqual :: RandomGen g => g -> IO Bool -runsEqual g = do - let (pureResult :: Word64) = runGenState_ g uniform - (genResult :: Word64) <- runMutGenIO_ g uniform - return $ pureResult == genResult +runsEqual g = pure True + -- let (pureResult :: Word64) = runGenState_ g uniform + -- (genResult :: Word64) <- runAtomicGen_ g uniform + -- return $ pureResult == genResult From 52561357b5a21e4f77871f1522e3acc3a47f4ecf Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Wed, 8 Apr 2020 05:18:47 +0300 Subject: [PATCH 097/170] General monadic apply to Pure gens --- System/Random.hs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 3aa78d36f..26d24c5cd 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -502,7 +502,7 @@ class RandomGen g where -- generators. split :: g -> (g, g) -class Monad m => MonadRandom (g :: * -> *) s m where +class Monad m => MonadRandom (g :: k -> *) s m where data Frozen g :: * {-# MINIMAL freezeGen,thawGen,(uniformWord32|uniformWord64) #-} @@ -532,6 +532,23 @@ class Monad m => MonadRandom (g :: * -> *) s m where uniformShortByteString n = genShortByteStringWith n . uniformWord64 {-# INLINE uniformShortByteString #-} +-- class (RandomGen r, MonadRandom (g r) s m) => RandomGenM (g :: * -> * -> *) r s m where +-- applyRandomGenM :: (r -> (a, r)) -> g r s -> m a + +-- splitRandomGenM :: RandomGenM g r s m => g r s -> m r +-- splitRandomGenM = applyRandomGenM split + +-- instance (RandomGen r, MonadIO m) => RandomGenM IOGen r r m where +-- applyRandomGenM = applyIOGen + +-- instance (RandomGen r, MonadIO m) => RandomGenM AtomicGen r r m where +-- applyRandomGenM = applyAtomicGen + +-- instance (RandomGen r, MonadState r m) => RandomGenM PureGen r r m where +-- applyRandomGenM f _ = state f + +-- instance RandomGen r => RandomGenM STGen r s (ST s) where +-- applyRandomGenM = applySTGen withGenM :: MonadRandom g s m => Frozen g -> (g s -> m a) -> m (a, Frozen g) withGenM fg action = do @@ -665,6 +682,7 @@ runGenStateT_ :: (RandomGen g, Functor f) => g -> (PureGen g g -> StateT g f a) runGenStateT_ g = fmap fst . runGenStateT g + -- | This is a wrapper around pure generator that can be used in an effectful environment. -- It is safe in presence of exceptions and concurrency since all operations are performed -- atomically. @@ -769,9 +787,9 @@ applySTGen f (STGenI ref) = do -- -- returned. -- -- -- -- @since 1.2 --- splitMutGen :: --- (RandomGen g, PrimMonad m, s ~ PrimState m) => MutGen g s -> m (MutGen g s) --- splitMutGen = atomicMutGen split >=> thawGen . MutGen +-- splitMGen :: +-- (RandomGen r, PrimMonad m, s ~ PrimState m) => g r -> m r +-- splitMGen = atomicMutGen split -- runMutGenST :: RandomGen g => g -> (forall s . MutGen g s -> ST s a) -> (a, g) -- runMutGenST g action = runST $ do From 10233ea4a8749730cd1d72fa5d285d5ac9856e86 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Wed, 8 Apr 2020 06:05:29 +0300 Subject: [PATCH 098/170] Remove dead code and add few STGen related functions --- System/Random.hs | 222 +++++++++++++++++----------------------------- tests/Spec/Run.hs | 12 +-- 2 files changed, 89 insertions(+), 145 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 26d24c5cd..9f07c4cdc 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -3,6 +3,7 @@ {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GHCForeignImportPrim #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE MultiParamTypeClasses #-} @@ -317,7 +318,10 @@ module System.Random RandomGen(..) , MonadRandom(..) , Frozen(..) - , withGenM + , runGenM + , runGenM_ + , RandomGenM(..) + , splitRandomGenM -- ** Standard random number generators , StdGen , mkStdGen @@ -333,23 +337,17 @@ module System.Random , runGenStateT_ , runPureGenST -- ** Mutable generators - -- *** AtomicGen - + -- *** AtomicGen , AtomicGen - -- , runAtomicGenST - -- , runAtomicGenST_ - -- , runAtomicGenIO - -- , runAtomicGenIO_ - -- , splitAtomicGen - -- , atomicAtomicGen + , applyAtomicGen + -- *** IOGen + , IOGen + , applyIOGen + -- *** STGen , STGen - -- -- *** PrimGen - unboxed mutable state - -- , PrimGen - -- , runPrimGenST - -- , runPrimGenST_ - -- , runPrimGenIO - -- , runPrimGenIO_ - -- , splitPrimGen - -- , applyPrimGen + , applySTGen + , runSTGen + , runSTGen_ -- ** The global random number generator @@ -502,7 +500,7 @@ class RandomGen g where -- generators. split :: g -> (g, g) -class Monad m => MonadRandom (g :: k -> *) s m where +class Monad m => MonadRandom (g :: * -> *) s m | m -> s where data Frozen g :: * {-# MINIMAL freezeGen,thawGen,(uniformWord32|uniformWord64) #-} @@ -532,31 +530,50 @@ class Monad m => MonadRandom (g :: k -> *) s m where uniformShortByteString n = genShortByteStringWith n . uniformWord64 {-# INLINE uniformShortByteString #-} --- class (RandomGen r, MonadRandom (g r) s m) => RandomGenM (g :: * -> * -> *) r s m where --- applyRandomGenM :: (r -> (a, r)) -> g r s -> m a +class (RandomGen r, MonadRandom (g r) s m) => RandomGenM (g :: * -> * -> *) r s m where + applyRandomGenM :: (r -> (a, r)) -> g r s -> m a --- splitRandomGenM :: RandomGenM g r s m => g r s -> m r --- splitRandomGenM = applyRandomGenM split +-- | Split a pure random number generator, update the mutable and get the splitted version +-- back +splitRandomGenM :: RandomGenM g r s m => g r s -> m r +splitRandomGenM = applyRandomGenM split --- instance (RandomGen r, MonadIO m) => RandomGenM IOGen r r m where --- applyRandomGenM = applyIOGen +instance (RandomGen r, MonadIO m) => RandomGenM IOGen r RealWorld m where + applyRandomGenM = applyIOGen --- instance (RandomGen r, MonadIO m) => RandomGenM AtomicGen r r m where --- applyRandomGenM = applyAtomicGen +instance (RandomGen r, MonadIO m) => RandomGenM AtomicGen r RealWorld m where + applyRandomGenM = applyAtomicGen --- instance (RandomGen r, MonadState r m) => RandomGenM PureGen r r m where --- applyRandomGenM f _ = state f +instance (RandomGen r, MonadState r m) => RandomGenM PureGen r r m where + applyRandomGenM f _ = state f --- instance RandomGen r => RandomGenM STGen r s (ST s) where --- applyRandomGenM = applySTGen +instance RandomGen r => RandomGenM STGen r s (ST s) where + applyRandomGenM = applySTGen -withGenM :: MonadRandom g s m => Frozen g -> (g s -> m a) -> m (a, Frozen g) -withGenM fg action = do +-- | Run a mutable generator by giving it a frozen seed. +-- +-- >>> import Data.Int (Int8) +-- >>> runGenM (IOGen (mkStdGen 217)) (`uniformListM` 5) :: IO ([Int8], Frozen (IOGen StdGen)) +-- ([-74,37,-50,-2,3],IOGen {unIOGen = SMGen 4273268533320920145 15251669095119325999}) +-- +-- @since 1.2 +runGenM :: MonadRandom g s m => Frozen g -> (g s -> m a) -> m (a, Frozen g) +runGenM fg action = do g <- thawGen fg res <- action g fg' <- freezeGen g pure (res, fg') + +-- | Same as `runGenM`, except drops the frozen generator +-- +-- @since 1.2 +runGenM_ :: MonadRandom g s m => Frozen g -> (g s -> m a) -> m a +runGenM_ fg action = fst <$> runGenM fg action + +-- | Generate a list with random values +-- +-- @since 1.2 uniformListM :: (MonadRandom g s m, Uniform a) => g s -> Int -> m [a] uniformListM gen n = replicateM n (uniform gen) @@ -690,8 +707,9 @@ runGenStateT_ g = fmap fst . runGenStateT g -- @since 1.2 newtype AtomicGen g s = AtomicGenI (IORef g) -instance (MonadIO m, RandomGen g) => MonadRandom (AtomicGen g) g m where - newtype Frozen (AtomicGen g) = AtomicGen g +instance (MonadIO m, RandomGen g) => MonadRandom (AtomicGen g) RealWorld m where + newtype Frozen (AtomicGen g) = AtomicGen { unAtomicGen :: g } + deriving (Eq, Show, Read) thawGen (AtomicGen g) = fmap AtomicGenI (liftIO $ newIORef g) freezeGen (AtomicGenI gVar) = fmap AtomicGen (liftIO $ readIORef gVar) uniformWord32R r = applyAtomicGen (genWord32R r) @@ -709,7 +727,7 @@ instance (MonadIO m, RandomGen g) => MonadRandom (AtomicGen g) g m where uniformShortByteString n = applyAtomicGen (genShortByteString n) -- | Apply a pure operation to generator atomically. -applyAtomicGen :: MonadIO m => (g -> (a, g)) -> AtomicGen g g -> m a +applyAtomicGen :: MonadIO m => (g -> (a, g)) -> AtomicGen g RealWorld -> m a applyAtomicGen op (AtomicGenI gVar) = liftIO $ atomicModifyIORef' gVar $ \g -> case op g of @@ -721,11 +739,25 @@ applyAtomicGen op (AtomicGenI gVar) = -- `AtomicGen` wrapper, since atomic modification is not being used with `IOGen`. Which also -- means that it is not safe in a concurrent setting. -- +-- Both `IOGen` and `AtomicGen` are necessary when generation of random values happens in +-- `IO` and especially when dealing with exception handling and resource allocation, which is +-- where `StateT` should never be used. For example writing a random number of bytes into a +-- temporary file: +-- +-- >>> import UnliftIO.Temporary (withSystemTempFile) +-- >>> import Data.ByteString (hPutStr) +-- >>> let ioGen g = withSystemTempFile "foo.bin" $ \_ h -> uniformR (0, 100) g >>= flip uniformByteString g >>= hPutStr h +-- +-- and then run it: +-- +-- >>> runGenM_ (IOGen (mkStdGen 1729)) ioGen +-- -- @since 1.2 newtype IOGen g s = IOGenI (IORef g) -instance (RandomGen g, MonadIO m) => MonadRandom (IOGen g) g m where - newtype Frozen (IOGen g) = IOGen g +instance (RandomGen g, MonadIO m) => MonadRandom (IOGen g) RealWorld m where + newtype Frozen (IOGen g) = IOGen { unIOGen :: g } + deriving (Eq, Show, Read) thawGen (IOGen g) = fmap IOGenI (liftIO $ newIORef g) freezeGen (IOGenI gVar) = fmap IOGen (liftIO $ readIORef gVar) uniformWord32R r = applyIOGen (genWord32R r) @@ -743,7 +775,7 @@ instance (RandomGen g, MonadIO m) => MonadRandom (IOGen g) g m where uniformShortByteString n = applyIOGen (genShortByteString n) -- | Apply a pure operation to generator atomically. -applyIOGen :: MonadIO m => (g -> (a, g)) -> IOGen g g -> m a +applyIOGen :: MonadIO m => (g -> (a, g)) -> IOGen g RealWorld -> m a applyIOGen f (IOGenI ref) = liftIO $ do g <- readIORef ref case f g of @@ -758,7 +790,8 @@ applyIOGen f (IOGenI ref) = liftIO $ do newtype STGen g s = STGenI (STRef s g) instance RandomGen g => MonadRandom (STGen g) s (ST s) where - newtype Frozen (STGen g) = STGen g + newtype Frozen (STGen g) = STGen { unSTGen :: g } + deriving (Eq, Show, Read) thawGen (STGen g) = fmap STGenI (newSTRef g) freezeGen (STGenI gVar) = fmap STGen (readSTRef gVar) uniformWord32R r = applySTGen (genWord32R r) @@ -783,107 +816,18 @@ applySTGen f (STGenI ref) = do (!a, !g') -> a <$ writeSTRef ref g' {-# INLINE applySTGen #-} --- -- | Split `MutGen` into atomically updated current generator and a newly created that is --- -- returned. --- -- --- -- @since 1.2 --- splitMGen :: --- (RandomGen r, PrimMonad m, s ~ PrimState m) => g r -> m r --- splitMGen = atomicMutGen split - --- runMutGenST :: RandomGen g => g -> (forall s . MutGen g s -> ST s a) -> (a, g) --- runMutGenST g action = runST $ do --- mutGen <- thawGen $ MutGen g --- res <- action mutGen --- MutGen g' <- freezeGen mutGen --- pure (res, g') - --- -- | Same as `runMutGenST`, but discard the resulting generator. --- runMutGenST_ :: RandomGen g => g -> (forall s . MutGen g s -> ST s a) -> a --- runMutGenST_ g action = fst $ runMutGenST g action - --- -- | Both `PrimGen` and `MutGen` and their corresponding functions like 'runPrimGenIO' are --- -- necessary when generation of random values happens in `IO` and especially when dealing --- -- with exception handling and resource allocation, which is where `StateT` should never be --- -- used. For example writing a random number of bytes into a temporary file: --- -- --- -- >>> import UnliftIO.Temporary (withSystemTempFile) --- -- >>> import Data.ByteString (hPutStr) --- -- >>> let ioGen g = withSystemTempFile "foo.bin" $ \_ h -> uniformR (0, 100) g >>= flip uniformByteStringPrim g >>= hPutStr h --- -- --- -- and then run it: --- -- --- -- >>> runMutGenIO_ (mkStdGen 1729) ioGen --- -- --- runMutGenIO :: (RandomGen g, MonadIO m) => g -> (MutGen g RealWorld -> m a) -> m (a, g) --- runMutGenIO g action = do --- mutGen <- liftIO $ thawGen $ MutGen g --- res <- action mutGen --- MutGen g' <- liftIO $ freezeGen mutGen --- pure (res, g') --- {-# INLINE runMutGenIO #-} - --- -- | Same as `runMutGenIO`, but discard the resulting generator. --- runMutGenIO_ :: (RandomGen g, MonadIO m) => g -> (MutGen g RealWorld -> m a) -> m a --- runMutGenIO_ g action = fst <$> runMutGenIO g action --- {-# INLINE runMutGenIO_ #-} - - --- newtype PrimGen g s = PrimGenI (MutableByteArray s) - --- instance (s ~ PrimState m, PrimMonad m, RandomGen g, Prim g) => --- MonadRandom (PrimGen g) s m where --- newtype Frozen (PrimGen g) = PrimGen g --- thawGen (PrimGen g) = do --- ma <- newByteArray (Primitive.sizeOf g) --- writeByteArray ma 0 g --- pure $ PrimGenI ma --- freezeGen (PrimGenI ma) = PrimGen <$> readByteArray ma 0 --- uniformWord32R r = applyPrimGen (genWord32R r) --- uniformWord64R r = applyPrimGen (genWord64R r) --- uniformWord8 = applyPrimGen genWord8 --- uniformWord16 = applyPrimGen genWord16 --- uniformWord32 = applyPrimGen genWord32 --- uniformWord64 = applyPrimGen genWord64 --- uniformByteArray n = applyPrimGen (genByteArray n) - --- applyPrimGen :: (Prim g, PrimMonad m, s ~ PrimState m) => (g -> (a, g)) -> PrimGen g s -> m a --- applyPrimGen f (PrimGenI ma) = do --- g <- readByteArray ma 0 --- case f g of --- (res, g') -> res <$ writeByteArray ma 0 g' - --- -- | Split `PrimGen` into atomically updated current generator and a newly created that is --- -- returned. --- -- --- -- @since 1.2 --- splitPrimGen :: --- (Prim g, RandomGen g, PrimMonad m, s ~ PrimState m) --- => PrimGen g s --- -> m (PrimGen g s) --- splitPrimGen = applyPrimGen split >=> thawGen . PrimGen - --- runPrimGenST :: (Prim g, RandomGen g) => g -> (forall s . PrimGen g s -> ST s a) -> (a, g) --- runPrimGenST g action = runST $ do --- primGen <- thawGen $ PrimGen g --- res <- action primGen --- PrimGen g' <- freezeGen primGen --- pure (res, g') - --- -- | Same as `runPrimGenST`, but discard the resulting generator. --- runPrimGenST_ :: (Prim g, RandomGen g) => g -> (forall s . PrimGen g s -> ST s a) -> a --- runPrimGenST_ g action = fst $ runPrimGenST g action - --- runPrimGenIO :: (Prim g, RandomGen g, MonadIO m) => g -> (PrimGen g RealWorld -> m a) -> m (a, g) --- runPrimGenIO g action = do --- primGen <- liftIO $ thawGen $ PrimGen g --- res <- action primGen --- PrimGen g' <- liftIO $ freezeGen primGen --- pure (res, g') - --- -- | Same as `runPrimGenIO`, but discard the resulting generator. --- runPrimGenIO_ :: (Prim g, RandomGen g, MonadIO m) => g -> (PrimGen g RealWorld -> m a) -> m a --- runPrimGenIO_ g action = fst <$> runPrimGenIO g action +-- | Run ST action that uses mutable @STGen@ +-- +-- @since 1.2 +runSTGen :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> (a, g) +runSTGen g action = unSTGen <$> runST (runGenM (STGen g) action) + +-- | Same as runSTGen, except discards the final generator state. +-- +-- @since 1.2 +runSTGen_ :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> a +runSTGen_ g action = fst $ runSTGen g action + type StdGen = SM.SMGen diff --git a/tests/Spec/Run.hs b/tests/Spec/Run.hs index 2566224bf..a43e3fb34 100644 --- a/tests/Spec/Run.hs +++ b/tests/Spec/Run.hs @@ -1,12 +1,12 @@ -{-# LANGUAGE ScopedTypeVariables #-} - module Spec.Run (runsEqual) where import Data.Word (Word64) import System.Random runsEqual :: RandomGen g => g -> IO Bool -runsEqual g = pure True - -- let (pureResult :: Word64) = runGenState_ g uniform - -- (genResult :: Word64) <- runAtomicGen_ g uniform - -- return $ pureResult == genResult +runsEqual g = do + let pureResult = runGenState_ g uniform :: Word64 + stResult = runSTGen_ g uniform + ioResult <- runGenM_ (IOGen g) uniform + atomicResult <- runGenM_ (AtomicGen g) uniform + return $ all (pureResult ==) [stResult, ioResult, atomicResult] From 920ab63ceb456e32c0beec67556c0c9cb129af7b Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Wed, 8 Apr 2020 14:19:31 +0100 Subject: [PATCH 099/170] Past participle --- System/Random.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/System/Random.hs b/System/Random.hs index 9f07c4cdc..09021135c 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -533,7 +533,7 @@ class Monad m => MonadRandom (g :: * -> *) s m | m -> s where class (RandomGen r, MonadRandom (g r) s m) => RandomGenM (g :: * -> * -> *) r s m where applyRandomGenM :: (r -> (a, r)) -> g r s -> m a --- | Split a pure random number generator, update the mutable and get the splitted version +-- | Split a pure random number generator, update the mutable and get the split version -- back splitRandomGenM :: RandomGenM g r s m => g r s -> m r splitRandomGenM = applyRandomGenM split From 8b81cca9438d31d99dfd376e0bfc7f412b3ffb18 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Wed, 8 Apr 2020 17:11:08 +0300 Subject: [PATCH 100/170] Spell fix Co-Authored-By: Leonhard Markert --- System/Random.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/System/Random.hs b/System/Random.hs index 09021135c..62b614d9e 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -735,7 +735,7 @@ applyAtomicGen op (AtomicGenI gVar) = {-# INLINE applyAtomicGen #-} -- | This is a wrapper wround an @IORef@ that holds a pure generator. Because of extra pointer --- indirection it will be slightly slower than if `PureGen` is being used, but faster then +-- indirection it will be slightly slower than if `PureGen` is being used, but faster than -- `AtomicGen` wrapper, since atomic modification is not being used with `IOGen`. Which also -- means that it is not safe in a concurrent setting. -- From 9421736b33123135b05d14b9daa0b67004d60289 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Wed, 8 Apr 2020 17:11:40 +0300 Subject: [PATCH 101/170] Fix haddock Co-Authored-By: Leonhard Markert --- System/Random.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/System/Random.hs b/System/Random.hs index 62b614d9e..727a5cec4 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -774,7 +774,7 @@ instance (RandomGen g, MonadIO m) => MonadRandom (IOGen g) RealWorld m where {-# INLINE uniformWord64 #-} uniformShortByteString n = applyIOGen (genShortByteString n) --- | Apply a pure operation to generator atomically. +-- | Apply a pure operation to the generator. applyIOGen :: MonadIO m => (g -> (a, g)) -> IOGen g RealWorld -> m a applyIOGen f (IOGenI ref) = liftIO $ do g <- readIORef ref From 0d6ec6c1af4a633e4f1e59ad4423d157cb801c4d Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Wed, 8 Apr 2020 17:42:32 +0300 Subject: [PATCH 102/170] Fix doctest an ShortByteString Generation in ST --- System/Random.hs | 37 ++++++++++++++++++++++--------------- random.cabal | 1 + 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 727a5cec4..aa57dd8a6 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -163,7 +163,7 @@ -- 'Prim', you can also use 'runMutGenIO' or 'runMutGenST' and their variants. -- -- >>> let pureGen = mkStdGen 42 --- >>> runPrimGenIO_ pureGen (rolls 10) :: IO [Word8] +-- >>> runGenM_ (IOGen pureGen) (rolls 10) :: IO [Word8] -- [1,1,3,2,4,5,3,4,6,2] -- -- = How to generate random values in pure code @@ -299,13 +299,14 @@ -- from the @mwc-random@ package: -- -- > instance (s ~ PrimState m, PrimMonad m) => MonadRandom MWC.Gen s m where --- > newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } --- > thawGen = fmap MWC.restore unFrozen --- > freezeGen = fmap Frozen . MWC.save --- > uniformWord8 = MWC.uniform --- > uniformWord16 = MWC.uniform --- > uniformWord32 = MWC.uniform --- > uniformWord64 = MWC.uniform +-- > newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } +-- > thawGen = fmap MWC.restore unFrozen +-- > freezeGen = fmap Frozen . MWC.save +-- > uniformWord8 = MWC.uniform +-- > uniformWord16 = MWC.uniform +-- > uniformWord32 = MWC.uniform +-- > uniformWord64 = MWC.uniform +-- > uniformShortByteString n g = unsafeSTToPrim (genShortByteStringST n (MWC.uniform g)) ----------------------------------------------------------------------------- module System.Random @@ -367,6 +368,7 @@ module System.Random -- * Generators for sequences of bytes , genShortByteStringWith + , genShortByteStringST , uniformByteString , genByteString @@ -377,6 +379,7 @@ module System.Random import Control.Arrow import Control.Monad.IO.Class import Control.Monad.ST +import Control.Monad.ST.Unsafe import Control.Monad.State.Strict import Data.Bits import Data.ByteString.Builder.Prim (word64LE) @@ -423,13 +426,14 @@ import GHC.IO (IO(..)) -- -- >>> :{ -- instance (s ~ PrimState m, PrimMonad m) => MonadRandom MWC.Gen s m where --- newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } --- thawGen = fmap MWC.restore unFrozen --- freezeGen = fmap Frozen . MWC.save --- uniformWord8 = MWC.uniform --- uniformWord16 = MWC.uniform --- uniformWord32 = MWC.uniform --- uniformWord64 = MWC.uniform +-- newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } +-- thawGen = fmap MWC.restore unFrozen +-- freezeGen = fmap Frozen . MWC.save +-- uniformWord8 = MWC.uniform +-- uniformWord16 = MWC.uniform +-- uniformWord32 = MWC.uniform +-- uniformWord64 = MWC.uniform +-- uniformShortByteString n g = unsafeSTToPrim (genShortByteStringST n (MWC.uniform g)) -- :} -- | The class 'RandomGen' provides a common interface to random number @@ -620,6 +624,9 @@ genShortByteStringWith n0 gen64 = do (# s'#, ba# #) -> (# s'#, SBS ba# #) {-# INLINE genShortByteStringWith #-} +genShortByteStringST :: Int -> ST s Word64 -> ST s ShortByteString +genShortByteStringST n action = + unsafeIOToST (genShortByteStringWith n (unsafeSTToIO action)) pinnedByteArrayToByteString :: ByteArray# -> ByteString pinnedByteArrayToByteString ba# = diff --git a/random.cabal b/random.cabal index 244d49784..905e72098 100644 --- a/random.cabal +++ b/random.cabal @@ -65,6 +65,7 @@ test-suite doctests base -any, doctest >=0.15, mwc-random -any, + primitive -any, random -any, unliftio -any From dd94497400ba3d929a7b32ac724a95b8dc2222dc Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Wed, 8 Apr 2020 20:29:43 +0300 Subject: [PATCH 103/170] Drop kind annotations --- System/Random.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index aa57dd8a6..7e8dcc493 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -504,7 +504,7 @@ class RandomGen g where -- generators. split :: g -> (g, g) -class Monad m => MonadRandom (g :: * -> *) s m | m -> s where +class Monad m => MonadRandom g s m | m -> s where data Frozen g :: * {-# MINIMAL freezeGen,thawGen,(uniformWord32|uniformWord64) #-} @@ -534,7 +534,7 @@ class Monad m => MonadRandom (g :: * -> *) s m | m -> s where uniformShortByteString n = genShortByteStringWith n . uniformWord64 {-# INLINE uniformShortByteString #-} -class (RandomGen r, MonadRandom (g r) s m) => RandomGenM (g :: * -> * -> *) r s m where +class (RandomGen r, MonadRandom (g r) s m) => RandomGenM g r s m where applyRandomGenM :: (r -> (a, r)) -> g r s -> m a -- | Split a pure random number generator, update the mutable and get the split version From 7a144b0426330038c02246068a2b848c009e1a8b Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Wed, 8 Apr 2020 20:31:06 +0300 Subject: [PATCH 104/170] Drop kind annotations --- System/Random.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/System/Random.hs b/System/Random.hs index 01d50c404..0d1b951b5 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -518,7 +518,7 @@ class RandomGen g where -- generators. split :: g -> (g, g) -class Monad m => MonadRandom (g :: * -> *) s m | m -> s where +class Monad m => MonadRandom g s m | m -> s where data Frozen g :: * {-# MINIMAL freezeGen,thawGen,(uniformWord32|uniformWord64) #-} From 3a8e77f5573b3c11f02586d4d17227954e26f634 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 9 Apr 2020 12:10:22 +0200 Subject: [PATCH 105/170] Edit Haddocks --- System/Random.hs | 290 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 206 insertions(+), 84 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 7e8dcc493..8d98cc853 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -67,18 +67,21 @@ -- -- * The methods 'next' and 'genRange' in 'RandomGen' are deprecated and only -- provided for backwards compatibility. New instances of 'RandomGen' should --- implement word-based methods instead. See below for more information about --- how to write a 'RandomGen' instance. +-- implement word-based methods instead. See below for more information +-- about how to write a 'RandomGen' instance. -- -- * This library provides instances for 'Random' for some unbounded datatypes --- for backwards compatibility. For an unbounded datatype, there is no way to --- generate a value with uniform probability out of its entire domain, so the --- 'random' implementation for unbounded datatypes actually generates a value --- based on some fixed range. +-- for backwards compatibility. For an unbounded datatype, there is no way +-- to generate a value with uniform probability out of its entire domain, so +-- the 'random' implementation for unbounded datatypes actually generates a +-- value based on some fixed range. -- --- For 'Integer', 'random' generates a value in the 'Int' range. For 'Float' and 'Double', 'random' generates a floating point value in the range @[0, 1)@. +-- For 'Integer', 'random' generates a value in the 'Int' range. For 'Float' +-- and 'Double', 'random' generates a floating point value in the range @[0, +-- 1)@. -- --- This library does not provide 'Uniform' instances for any unbounded datatypes. +-- This library does not provide 'Uniform' instances for any unbounded +-- datatypes. -- -- = Reproducibility -- @@ -312,13 +315,10 @@ module System.Random ( - -- $intro - -- * Random number generators RandomGen(..) , MonadRandom(..) - , Frozen(..) , runGenM , runGenM_ , RandomGenM(..) @@ -436,32 +436,48 @@ import GHC.IO (IO(..)) -- uniformShortByteString n g = unsafeSTToPrim (genShortByteStringST n (MWC.uniform g)) -- :} --- | The class 'RandomGen' provides a common interface to random number --- generators. +-- | 'RandomGen' is an interface to pure pseudo-random number generators. +-- +-- 'StdGen' is the default 'RandomGen' instance provided by this library. {-# DEPRECATED next "No longer used" #-} {-# DEPRECATED genRange "No longer used" #-} class RandomGen g where {-# MINIMAL split,(genWord32|genWord64|(next,genRange)) #-} - -- |The 'next' operation returns an 'Int' that is uniformly - -- distributed in the range returned by 'genRange' (including both - -- end points), and a new generator. Using 'next' is inefficient as - -- all operations go via 'Integer'. See - -- [here](https://alexey.kuleshevi.ch/blog/2019/12/21/random-benchmarks) - -- for more details. It is thus deprecated. + -- | Returns an 'Int' that is uniformly distributed over the range returned by + -- 'genRange' (including both end points), and a new generator. Using 'next' + -- is inefficient as all operations go via 'Integer'. See + -- [here](https://alexey.kuleshevi.ch/blog/2019/12/21/random-benchmarks) for + -- more details. It is thus deprecated. next :: g -> (Int, g) next g = runGenState g (uniformR (genRange g)) + -- | Returns a 'Word8' that is uniformly distributed over the entire 'Word8' + -- range. + -- + -- @since 1.2 genWord8 :: g -> (Word8, g) genWord8 = first fromIntegral . genWord32 + -- | Returns a 'Word16' that is uniformly distributed over the entire 'Word16' + -- range. + -- + -- @since 1.2 genWord16 :: g -> (Word16, g) genWord16 = first fromIntegral . genWord32 + -- | Returns a 'Word32' that is uniformly distributed over the entire 'Word32' + -- range. + -- + -- @since 1.2 genWord32 :: g -> (Word32, g) genWord32 = randomIvalIntegral (minBound, maxBound) -- Once `next` is removed, this implementation should be used instead: -- first fromIntegral . genWord64 + -- | Returns a 'Word64' that is uniformly distributed over the entire 'Word64' + -- range. + -- + -- @since 1.2 genWord64 :: g -> (Word64, g) genWord64 g = case genWord32 g of @@ -470,75 +486,144 @@ class RandomGen g where (h32, g'') -> ((fromIntegral h32 `unsafeShiftL` 32) .|. fromIntegral l32, g'') + -- | @genWord32R upperBound g@ returns a 'Word32' that is uniformly + -- distributed over the range @[0, upperBound]@. + -- + -- @since 1.2 genWord32R :: Word32 -> g -> (Word32, g) genWord32R m g = runGenState g (unbiasedWordMult32 m) + -- | @genWord64R upperBound g@ returns a 'Word64' that is uniformly + -- distributed over the range @[0, upperBound]@. + -- + -- @since 1.2 genWord64R :: Word64 -> g -> (Word64, g) genWord64R m g = runGenState g (unsignedBitmaskWithRejectionM uniformWord64 m) + -- | @genShortByteString n g@ returns a 'ShortByteString' of length @n@ + -- filled with random bytes. + -- + -- @since 1.2 genShortByteString :: Int -> g -> (ShortByteString, g) genShortByteString n g = unsafePerformIO $ runGenStateT g (genShortByteStringWith n . uniformWord64) {-# INLINE genShortByteString #-} - -- |The 'genRange' operation yields the range of values returned by - -- the generator. + + -- | Yields the range of values returned by 'next'. -- -- It is required that: -- - -- * If @(a, b) = 'genRange' g@, then @a <= b@. - -- - -- * 'genRange' always returns a pair of defined 'Int's. - -- - -- The second condition ensures that 'genRange' cannot examine its - -- argument, and hence the value it returns can be determined only by the - -- instance of 'RandomGen'. That in turn allows an implementation to make - -- a single call to 'genRange' to establish a generator's range, without - -- being concerned that the generator returned by (say) 'next' might have - -- a different range to the generator passed to 'next'. + -- * If @(a, b) = 'genRange' g@, then @a < b@. + -- * 'genRange' must not examine its argument so the value it returns is + -- determined only by the instance of 'RandomGen'. -- -- The default definition spans the full range of 'Int'. genRange :: g -> (Int, Int) genRange _ = (minBound, maxBound) - -- | The 'split' operation allows one to obtain two distinct random number - -- generators. + -- | Returns two distinct pseudo-random number generators. + -- + -- Implementations should take care to ensure that the resulting generators + -- are not correlated. Some pseudo-random number generators are not + -- splittable. In that case, the 'split' implementation should fail with a + -- descriptive 'error' message. split :: g -> (g, g) +-- | 'MonadRandom' is an interface to monadic pseudo-random number generators. class Monad m => MonadRandom g s m | m -> s where + -- | Represents the state of the pseudo-random number generator for use with + -- 'thawGen' and 'freezeGen'. + -- + -- @since 1.2 data Frozen g :: * {-# MINIMAL freezeGen,thawGen,(uniformWord32|uniformWord64) #-} + -- | Restores the pseudo-random number generator from its 'Frozen' + -- representation. + -- + -- @since 1.2 thawGen :: Frozen g -> m (g s) + + -- | Saves the state of the pseudo-random number generator to its 'Frozen' + -- representation. + -- + -- @since 1.2 freezeGen :: g s -> m (Frozen g) - -- | Generate 'Word32' up to and including the supplied max value + + -- | @uniformWord32R upperBound g@ generates a 'Word32' that is uniformly + -- distributed over the range @[0, upperBound]@. + -- + -- @since 1.2 uniformWord32R :: Word32 -> g s -> m Word32 uniformWord32R = unsignedBitmaskWithRejectionM uniformWord32 - -- | Generate 'Word64' up to and including the supplied max value + -- | @uniformWord64R upperBound g@ generates a 'Word64' that is uniformly + -- distributed over the range @[0, upperBound]@. + -- + -- @since 1.2 uniformWord64R :: Word64 -> g s -> m Word64 uniformWord64R = unsignedBitmaskWithRejectionM uniformWord64 + -- | Generates a 'Word8' that is uniformly distributed over the entire 'Word8' + -- range. + -- + -- The default implementation extracts a 'Word8' from 'uniformWord32'. + -- + -- @since 1.2 uniformWord8 :: g s -> m Word8 uniformWord8 = fmap fromIntegral . uniformWord32 + + -- | Generates a 'Word16' that is uniformly distributed over the entire + -- 'Word16' range. + -- + -- The default implementation extracts a 'Word16' from 'uniformWord32'. + -- + -- @since 1.2 uniformWord16 :: g s -> m Word16 uniformWord16 = fmap fromIntegral . uniformWord32 + + -- | Generates a 'Word32' that is uniformly distributed over the entire + -- 'Word32' range. + -- + -- The default implementation extracts a 'Word32' from 'uniformWord64'. + -- + -- @since 1.2 uniformWord32 :: g s -> m Word32 uniformWord32 = fmap fromIntegral . uniformWord64 + + -- | Generates a 'Word64' that is uniformly distributed over the entire + -- 'Word64' range. + -- + -- The default implementation combines two 'Word32' from 'uniformWord32' into + -- one 'Word64'. + -- + -- @since 1.2 uniformWord64 :: g s -> m Word64 uniformWord64 g = do l32 <- uniformWord32 g h32 <- uniformWord32 g pure (unsafeShiftL (fromIntegral h32) 32 .|. fromIntegral l32) + + -- | @uniformShortByteString n g@ generates a 'ShortByteString' of length @n@ + -- filled with random bytes. + -- + -- @since 1.2 uniformShortByteString :: Int -> g s -> m ShortByteString default uniformShortByteString :: MonadIO m => Int -> g s -> m ShortByteString uniformShortByteString n = genShortByteStringWith n . uniformWord64 {-# INLINE uniformShortByteString #-} + +-- | Interface to operations on 'RandomGen' wrappers like 'IOGen' and 'PureGen'. +-- +-- @since 1.2 class (RandomGen r, MonadRandom (g r) s m) => RandomGenM g r s m where applyRandomGenM :: (r -> (a, r)) -> g r s -> m a --- | Split a pure random number generator, update the mutable and get the split version --- back +-- | Splits a pseudo-random number generator into two. Overwrites the mutable +-- wrapper with one of the resulting generators and returns the other. +-- +-- @since 1.2 splitRandomGenM :: RandomGenM g r s m => g r s -> m r splitRandomGenM = applyRandomGenM split @@ -554,7 +639,7 @@ instance (RandomGen r, MonadState r m) => RandomGenM PureGen r r m where instance RandomGen r => RandomGenM STGen r s (ST s) where applyRandomGenM = applySTGen --- | Run a mutable generator by giving it a frozen seed. +-- | Runs a mutable pseudo-random number generator from its 'Frozen' state. -- -- >>> import Data.Int (Int8) -- >>> runGenM (IOGen (mkStdGen 217)) (`uniformListM` 5) :: IO ([Int8], Frozen (IOGen StdGen)) @@ -568,14 +653,13 @@ runGenM fg action = do fg' <- freezeGen g pure (res, fg') - --- | Same as `runGenM`, except drops the frozen generator +-- | Same as 'runGenM', but only returns the generated value. -- -- @since 1.2 runGenM_ :: MonadRandom g s m => Frozen g -> (g s -> m a) -> m a runGenM_ fg action = fst <$> runGenM fg action --- | Generate a list with random values +-- | Generates a list of random values. -- -- @since 1.2 uniformListM :: (MonadRandom g s m, Uniform a) => g s -> Int -> m [a] @@ -584,9 +668,11 @@ uniformListM gen n = replicateM n (uniform gen) data MBA s = MBA (MutableByteArray# s) --- | This function will efficiently generate a sequence of random bytes in a platform --- independent manner. Memory allocated will be pinned, so it is safe to use with FFI +-- | Efficiently generates a sequence of random bytes in a platform independent +-- manner. The allocated memory is be pinned, so it is safe to use with FFI -- calls. +-- +-- @since 1.2 genShortByteStringWith :: MonadIO m => Int -> m Word64 -> m ShortByteString genShortByteStringWith n0 gen64 = do let !n@(I# n#) = max 0 n0 @@ -599,19 +685,19 @@ genShortByteStringWith n0 gen64 = do let go i ptr | i < n64 = do w64 <- gen64 - -- Writing 8 bytes at a time in a Little-endian order gives us platform - -- portability + -- Writing 8 bytes at a time in a Little-endian order gives us + -- platform portability liftIO $ runF word64LE w64 ptr go (i + 1) (ptr `plusPtr` 8) | otherwise = return ptr ptr <- go 0 (Ptr (byteArrayContents# (unsafeCoerce# mba#))) when (nrem64 > 0) $ do w64 <- gen64 - -- In order to not mess up the byte order we write generated Word64 into a temporary - -- pointer and then copy only the missing bytes over to the array. It is tempting to - -- simply generate as many bytes as we still need using smaller generators - -- (eg. uniformWord8), but that would result in inconsistent tail when total length is - -- slightly varied. + -- In order to not mess up the byte order we write generated Word64 into a + -- temporary pointer and then copy only the missing bytes over to the array. + -- It is tempting to simply generate as many bytes as we still need using + -- smaller generators (eg. uniformWord8), but that would result in + -- inconsistent tail when total length is slightly varied. liftIO $ alloca $ \w64ptr -> do runF word64LE w64 w64ptr @@ -624,6 +710,9 @@ genShortByteStringWith n0 gen64 = do (# s'#, ba# #) -> (# s'#, SBS ba# #) {-# INLINE genShortByteStringWith #-} +-- | Same as 'genShortByteStringWith', but runs in 'ST'. +-- +-- @since 1.2 genShortByteStringST :: Int -> ST s Word64 -> ST s ShortByteString genShortByteStringST n action = unsafeIOToST (genShortByteStringWith n (unsafeSTToIO action)) @@ -638,7 +727,7 @@ pinnedByteArrayToForeignPtr ba# = ForeignPtr (byteArrayContents# ba#) (PlainPtr (unsafeCoerce# ba#)) {-# INLINE pinnedByteArrayToForeignPtr #-} --- | Generate a random ByteString of specified size. +-- | Generates a random 'ByteString' of the specified size. -- -- @since 1.2 uniformByteString :: MonadRandom g s m => Int -> g s -> m ByteString @@ -650,15 +739,16 @@ uniformByteString n g = do else fromShort ba {-# INLINE uniformByteString #-} --- | Generate a ByteString using a pure generator. For monadic counterpart see --- `uniformByteStringPrim`. +-- | Generates a 'ByteString' of the specified size using a pure pseudo-random +-- number generator. See 'uniformByteString' for the monadic version. -- -- @since 1.2 genByteString :: RandomGen g => Int -> g -> (ByteString, g) genByteString n g = runPureGenST g (uniformByteString n) {-# INLINE genByteString #-} --- | Run an effectful generating action in `ST` monad using a pure generator. +-- | Runs an effectful generating action in the `ST` monad using a pure +-- pseudo-random number generator. -- -- @since 1.2 runPureGenST :: RandomGen g => g -> (forall s . PureGen g g -> StateT g (ST s) a) -> (a, g) @@ -666,10 +756,13 @@ runPureGenST g action = runST $ runGenStateT g $ action {-# INLINE runPureGenST #-} --- | An opaque data type that carries the type of a pure generator +-- | Opaque data type that carries the type of a pure pseudo-random number +-- generator. +-- +-- @since 1.2 data PureGen g s = PureGenI -instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) g m where +instance (RandomGen g, MonadState g m) => MonadRandom (PureGen g) g m where newtype Frozen (PureGen g) = PureGen g thawGen (PureGen g) = PureGenI <$ put g freezeGen _ = fmap PureGen get @@ -681,40 +774,55 @@ instance (MonadState g m, RandomGen g) => MonadRandom (PureGen g) g m where uniformWord64 _ = state genWord64 uniformShortByteString n _ = state (genShortByteString n) --- | Generate a random value in a state monad +-- | Generates a random value in a state monad. -- -- @since 1.2 genRandom :: (RandomGen g, Random a, MonadState g m) => PureGen g g -> m a genRandom = randomM --- | Split current generator and update the state with one part, while returning the other. +-- | Splits a pseudo-random number generator into two. Updates the state with +-- one of the resulting generators and returns the other. -- -- @since 1.2 splitGen :: (MonadState g m, RandomGen g) => m g splitGen = state split +-- | Runs an effectful generating action in the `State` monad using a pure +-- pseudo-random number generator. +-- +-- @since 1.2 runGenState :: RandomGen g => g -> (PureGen g g -> State g a) -> (a, g) runGenState g f = runState (f PureGenI) g +-- | Runs an effectful generating action in the `State` monad using a pure +-- pseudo-random number generator. Returns only the resulting random value. +-- +-- @since 1.2 runGenState_ :: RandomGen g => g -> (PureGen g g -> State g a) -> a runGenState_ g = fst . runGenState g +-- | Runs an effectful generating action in the `StateT` monad using a pure +-- pseudo-random number generator. +-- +-- @since 1.2 runGenStateT :: RandomGen g => g -> (PureGen g g -> StateT g m a) -> m (a, g) runGenStateT g f = runStateT (f PureGenI) g +-- | Runs an effectful generating action in the `StateT` monad using a pure +-- pseudo-random number generator. Returns only the resulting random value. +-- +-- @since 1.2 runGenStateT_ :: (RandomGen g, Functor f) => g -> (PureGen g g -> StateT g f a) -> f a runGenStateT_ g = fmap fst . runGenStateT g - - --- | This is a wrapper around pure generator that can be used in an effectful environment. --- It is safe in presence of exceptions and concurrency since all operations are performed --- atomically. +-- | This is a wrapper around pure generator that can be used in an effectful +-- environment. It is safe in presence of exceptions and concurrency since all +-- operations are performed atomically. -- -- @since 1.2 newtype AtomicGen g s = AtomicGenI (IORef g) -instance (MonadIO m, RandomGen g) => MonadRandom (AtomicGen g) RealWorld m where +instance (RandomGen g, MonadIO m) => MonadRandom (AtomicGen g) RealWorld m where newtype Frozen (AtomicGen g) = AtomicGen { unAtomicGen :: g } deriving (Eq, Show, Read) thawGen (AtomicGen g) = fmap AtomicGenI (liftIO $ newIORef g) @@ -733,7 +841,10 @@ instance (MonadIO m, RandomGen g) => MonadRandom (AtomicGen g) RealWorld m where {-# INLINE uniformWord64 #-} uniformShortByteString n = applyAtomicGen (genShortByteString n) --- | Apply a pure operation to generator atomically. +-- | Atomically applies a pure operation to the wrapped pseudo-random number +-- generator. +-- +-- @since 1.2 applyAtomicGen :: MonadIO m => (g -> (a, g)) -> AtomicGen g RealWorld -> m a applyAtomicGen op (AtomicGenI gVar) = liftIO $ atomicModifyIORef' gVar $ \g -> @@ -741,15 +852,16 @@ applyAtomicGen op (AtomicGenI gVar) = (a, g') -> (g', a) {-# INLINE applyAtomicGen #-} --- | This is a wrapper wround an @IORef@ that holds a pure generator. Because of extra pointer --- indirection it will be slightly slower than if `PureGen` is being used, but faster than --- `AtomicGen` wrapper, since atomic modification is not being used with `IOGen`. Which also --- means that it is not safe in a concurrent setting. +-- | This is a wrapper around an @IORef@ that holds a pure generator. Because of +-- extra pointer indirection it will be slightly slower than if `PureGen` is +-- being used, but faster than `AtomicGen` wrapper, since atomic modification is +-- not being used with `IOGen`. Which also means that it is not safe in a +-- concurrent setting. -- --- Both `IOGen` and `AtomicGen` are necessary when generation of random values happens in --- `IO` and especially when dealing with exception handling and resource allocation, which is --- where `StateT` should never be used. For example writing a random number of bytes into a --- temporary file: +-- Both `IOGen` and `AtomicGen` are necessary when generation of random values +-- happens in `IO` and especially when dealing with exception handling and +-- resource allocation, which is where `StateT` should never be used. For +-- example writing a random number of bytes into a temporary file: -- -- >>> import UnliftIO.Temporary (withSystemTempFile) -- >>> import Data.ByteString (hPutStr) @@ -781,7 +893,9 @@ instance (RandomGen g, MonadIO m) => MonadRandom (IOGen g) RealWorld m where {-# INLINE uniformWord64 #-} uniformShortByteString n = applyIOGen (genShortByteString n) --- | Apply a pure operation to the generator. +-- | Applies a pure operation to the wrapped pseudo-random number generator. +-- +-- @since 1.2 applyIOGen :: MonadIO m => (g -> (a, g)) -> IOGen g RealWorld -> m a applyIOGen f (IOGenI ref) = liftIO $ do g <- readIORef ref @@ -790,8 +904,9 @@ applyIOGen f (IOGenI ref) = liftIO $ do {-# INLINE applyIOGen #-} --- | This is a wrapper wround an @STRef@ that holds a pure generator. Because of extra pointer --- indirection it will be slightly slower than if `PureGen` is being used. +-- | This is a wrapper wround an @STRef@ that holds a pure generator. Because of +-- extra pointer indirection it will be slightly slower than if `PureGen` is +-- being used. -- -- @since 1.2 newtype STGen g s = STGenI (STRef s g) @@ -815,7 +930,9 @@ instance RandomGen g => MonadRandom (STGen g) s (ST s) where {-# INLINE uniformWord64 #-} uniformShortByteString n = applySTGen (genShortByteString n) --- | Apply a pure operation to generator atomically. +-- | Applies a pure operation to the wrapped pseudo-random number generator. +-- +-- @since 1.2 applySTGen :: (g -> (a, g)) -> STGen g s -> ST s a applySTGen f (STGenI ref) = do g <- readSTRef ref @@ -823,19 +940,21 @@ applySTGen f (STGenI ref) = do (!a, !g') -> a <$ writeSTRef ref g' {-# INLINE applySTGen #-} --- | Run ST action that uses mutable @STGen@ +-- | Runs an effectful generating action in the `ST` monad using a pure +-- pseudo-random number generator. -- -- @since 1.2 runSTGen :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> (a, g) runSTGen g action = unSTGen <$> runST (runGenM (STGen g) action) --- | Same as runSTGen, except discards the final generator state. +-- | Runs an effectful generating action in the `ST` monad using a pure +-- pseudo-random number generator. Returns only the resulting random value. -- -- @since 1.2 runSTGen_ :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> a runSTGen_ g action = fst $ runSTGen g action - +-- | The default pseudo-random number generator. type StdGen = SM.SMGen instance RandomGen StdGen where @@ -880,6 +999,8 @@ mkStdGen s = SM.mkSMGen $ fromIntegral s -- | Generate every possible value for data type with equal probability. +-- +-- @since 1.2 class Uniform a where uniform :: MonadRandom g s m => g s -> m a @@ -892,6 +1013,8 @@ class Uniform a where -- elements in range shouldn't matter and following law should hold: -- -- > uniformR (a,b) = uniform (b,a) +-- +-- @since 1.2 class UniformRange a where uniformR :: MonadRandom g s m => (a, a) -> g s -> m a @@ -908,7 +1031,7 @@ Minimal complete definition: 'randomR' and 'random'. class Random a where -- | Takes a range /(lo,hi)/ and a random number generator - -- /g/, and returns a random value uniformly distributed in the closed + -- /g/, and returns a random value uniformly distributed over the closed -- interval /[lo,hi]/, together with a new generator. It is unspecified -- what happens if /lo>hi/. For continuous types there is no requirement -- that the values /lo/ and /hi/ are ever produced, but they may be, @@ -1426,9 +1549,9 @@ unbiasedWordMult32 s g {-# INLINE unbiasedWordMult32 #-} -- | See [Lemire's paper](https://arxiv.org/pdf/1805.10941.pdf), --- [O'Neill's +-- [O\'Neill's -- blogpost](https://www.pcg-random.org/posts/bounded-rands.html) and --- more directly [O'Neill's github +-- more directly [O\'Neill's github -- repo](https://github.com/imneme/bounded-rands/blob/3d71f53c975b1e5b29f2f3b05a74e26dab9c3d84/bounded32.cpp#L234). -- N.B. The range is [0,t) **not** [0,t]. unbiasedWordMult32Exclusive :: MonadRandom g s m => Word32 -> g s -> m Word32 @@ -1525,7 +1648,6 @@ between 1 and 6: > rollDice = getStdRandom (randomR (1,6)) -} - getStdRandom :: (StdGen -> (a,StdGen)) -> IO a getStdRandom f = atomicModifyIORef' theStdGen (swap . f) where swap (v,g) = (g,v) From dc300ced56b6a1a538d596d3975a4f5bda0c39d6 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 9 Apr 2020 16:20:09 +0200 Subject: [PATCH 106/170] effectful -> monadic --- System/Random.hs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 8d98cc853..cf5dfa2d6 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -747,7 +747,7 @@ genByteString :: RandomGen g => Int -> g -> (ByteString, g) genByteString n g = runPureGenST g (uniformByteString n) {-# INLINE genByteString #-} --- | Runs an effectful generating action in the `ST` monad using a pure +-- | Runs a monadic generating action in the `ST` monad using a pure -- pseudo-random number generator. -- -- @since 1.2 @@ -787,35 +787,35 @@ genRandom = randomM splitGen :: (MonadState g m, RandomGen g) => m g splitGen = state split --- | Runs an effectful generating action in the `State` monad using a pure +-- | Runs a monadic generating action in the `State` monad using a pure -- pseudo-random number generator. -- -- @since 1.2 runGenState :: RandomGen g => g -> (PureGen g g -> State g a) -> (a, g) runGenState g f = runState (f PureGenI) g --- | Runs an effectful generating action in the `State` monad using a pure +-- | Runs a monadic generating action in the `State` monad using a pure -- pseudo-random number generator. Returns only the resulting random value. -- -- @since 1.2 runGenState_ :: RandomGen g => g -> (PureGen g g -> State g a) -> a runGenState_ g = fst . runGenState g --- | Runs an effectful generating action in the `StateT` monad using a pure +-- | Runs a monadic generating action in the `StateT` monad using a pure -- pseudo-random number generator. -- -- @since 1.2 runGenStateT :: RandomGen g => g -> (PureGen g g -> StateT g m a) -> m (a, g) runGenStateT g f = runStateT (f PureGenI) g --- | Runs an effectful generating action in the `StateT` monad using a pure +-- | Runs a monadic generating action in the `StateT` monad using a pure -- pseudo-random number generator. Returns only the resulting random value. -- -- @since 1.2 runGenStateT_ :: (RandomGen g, Functor f) => g -> (PureGen g g -> StateT g f a) -> f a runGenStateT_ g = fmap fst . runGenStateT g --- | This is a wrapper around pure generator that can be used in an effectful +-- | This is a wrapper around pure generator that can be used in a monadic -- environment. It is safe in presence of exceptions and concurrency since all -- operations are performed atomically. -- @@ -940,14 +940,14 @@ applySTGen f (STGenI ref) = do (!a, !g') -> a <$ writeSTRef ref g' {-# INLINE applySTGen #-} --- | Runs an effectful generating action in the `ST` monad using a pure +-- | Runs a monadic generating action in the `ST` monad using a pure -- pseudo-random number generator. -- -- @since 1.2 runSTGen :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> (a, g) runSTGen g action = unSTGen <$> runST (runGenM (STGen g) action) --- | Runs an effectful generating action in the `ST` monad using a pure +-- | Runs a monadic generating action in the `ST` monad using a pure -- pseudo-random number generator. Returns only the resulting random value. -- -- @since 1.2 From 073b19250018eb0946fdc5c497ef26e5bcb23506 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 9 Apr 2020 16:20:45 +0200 Subject: [PATCH 107/170] Remove double spaces --- System/Random.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index cf5dfa2d6..d8194278d 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -445,7 +445,7 @@ class RandomGen g where {-# MINIMAL split,(genWord32|genWord64|(next,genRange)) #-} -- | Returns an 'Int' that is uniformly distributed over the range returned by -- 'genRange' (including both end points), and a new generator. Using 'next' - -- is inefficient as all operations go via 'Integer'. See + -- is inefficient as all operations go via 'Integer'. See -- [here](https://alexey.kuleshevi.ch/blog/2019/12/21/random-benchmarks) for -- more details. It is thus deprecated. next :: g -> (Int, g) @@ -1456,7 +1456,7 @@ randomIvalInteger (l,h) rng b = fromIntegral genhi - fromIntegral genlo + 1 -- Probabilities of the most likely and least likely result - -- will differ at most by a factor of (1 +- 1/q). Assuming the RandomGen + -- will differ at most by a factor of (1 +- 1/q). Assuming the RandomGen -- is uniform, of course -- On average, log q / log b more random values will be generated From 5f9e7d2ce61f0853def8ee74c784120120c07103 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 10 Apr 2020 08:52:41 +0200 Subject: [PATCH 108/170] Export Frozen(..) after all --- System/Random.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/System/Random.hs b/System/Random.hs index d8194278d..f2bb0642a 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -319,6 +319,7 @@ module System.Random RandomGen(..) , MonadRandom(..) + , Frozen(..) , runGenM , runGenM_ , RandomGenM(..) From 1b73ee2ecd90905804df4cb710f1e91adb0c0a73 Mon Sep 17 00:00:00 2001 From: Alexey Khudyakov Date: Fri, 10 Apr 2020 16:29:51 +0300 Subject: [PATCH 109/170] Fix functional dependency conflict m -> s functional dependency is too weak and _does_ cause conflicts when different generators have different nodes in the same monad. For example adding following instance (as of 8215fd932292701a3eca0030451746978ca129d0) > data G1 s = G1 (IORef Int) > > instance MonadRandom G1 () IO where > newtype Frozen G1 = F1 Int causes error > System/Random.hs:826:10: error: > Functional dependencies conflict between instance declarations: > instance (RandomGen g, MonadIO m) => > MonadRandom (AtomicGen g) RealWorld m > -- Defined at System/Random.hs:826:10 > instance (RandomGen g, MonadIO m) => > MonadRandom (IOGen g) RealWorld m > -- Defined at System/Random.hs:878:10 > instance MonadRandom G1 () IO > -- Defined at System/Random.hs:1733:10 Fix is very simple however: replace `m -> s` by `g m -> s` which makes perfect sense, as state token type is determined by both g _and_ m. --- System/Random.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/System/Random.hs b/System/Random.hs index f2bb0642a..6eb9ccd7e 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -531,7 +531,7 @@ class RandomGen g where split :: g -> (g, g) -- | 'MonadRandom' is an interface to monadic pseudo-random number generators. -class Monad m => MonadRandom g s m | m -> s where +class Monad m => MonadRandom g s m | g m -> s where -- | Represents the state of the pseudo-random number generator for use with -- 'thawGen' and 'freezeGen'. -- From 12db946a35d05c5cf85ef52c0e00f76651029b65 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Wed, 8 Apr 2020 17:10:00 +0100 Subject: [PATCH 110/170] 1.1 vs 1.2 performance and better formatting --- CHANGELOG.md | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15c882af9..0c10d55be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,3 +24,70 @@ bump for bug fixes, # 1.0.0.4 bumped version for float/double range bugfix +# 1.2 + +1. Breaking change which mostly maintains backwards compatibility. +2. Faster by more x10 (depending on the type) - see below for benchmarks. +3. Passes a large number of random number test suites: + * [dieharder](http://webhome.phy.duke.edu/~rgb/General/dieharder.php "venerable") + * [TestU01 (SmallCrush, Crush, BigCrush)](http://simul.iro.umontreal.ca/testu01/tu01.html "venerable") + * [PractRand](http://pracrand.sourceforge.net/ "active") + * [gjrand](http://gjrand.sourceforge.net/ "active") + * [rademacher-fpl](https://gitlab.com/christoph-conrads/rademacher-fpl/-/tree/master "active") + * [gjrand](http://gjrand.sourceforge.net/ "active") + * See [random-quality](https://github.com/tweag/random-quality) + for details on how to do this yourself. +4. Better quality split as judged by these + [tests](https://www.cambridge.org/core/journals/journal-of-functional-programming/article/evaluation-of-splittable-pseudorandom-generators/3EBAA9F14939C5BB5560E32D1A132637). Again + see [random-quality](https://github.com/tweag/random-quality) for + details on how to do this yourself. +5. Unbiased generation of ranges. +6. Updated tests and benchmarks. +7. [Conntinuous integration](https://travis-ci.org/github/idontgetoutmuch/random). +8. Fully documented - for more details see the [haddock](https://htmlpreview.github.io/?https://github.com/idontgetoutmuch/random/blob/release-notes/docs/System-Random.html). + +## Benchmarks + +### Notes + +1. These are **not** percentage (%) increases. Random `Int`s are produced 48.9 times faster! +2. The only type for which generation is slower is for `Integer`s (on + ranges); in the version 1.1 the generation for `Integer` was + biased. + +### Without Specifying Ranges + + |----------|----------------|----------------|----------------------| + | Type | Cycles/Int 1.1 | Cycles/Int 1.2 | Performance Increase | + |----------|----------------|----------------|----------------------| + | Ints | 1508 | 30.84 | 48.9 | + | Word16 | 495 | 30.88 | 16.0 | + | Floats | 1036 | 35.11 | 29.5 | + | CFloats | 1054 | 33.75 | 31.2 | + | Doubles | 1875 | 35.77 | 52.4 | + | CDoubles | 908 | 33.31 | 27.3 | + | Integers | 1578 | 33.09 | 47.7 | + | Bools | 698 | 36.15 | 19.3 | + | Chars | 693 | 57.6 | 12.0 | + |----------|----------------|----------------|----------------------| + +### Specifying Ranges + + |--------------|----------------|----------------|----------------------| + | Type | Cycles/Int 1.1 | Cycles/Int 1.2 | Performance Increase | + |--------------|----------------|----------------|----------------------| + | Ints | 734 | 102 | 7.2 | + | Word16s | 748 | 115 | 6.5 | + | Floats | 2055 | 35.88 | 57.3 | + | CFloats | 1071 | 34.96 | 30.6 | + | Doubles | 3050 | 35.89 | 85.0 | + | CDoubles | 1112 | 34.87 | 31.9 | + | Integers | 534 | 868 | 0.6 | + | Bools | 739 | 35.22 | 21.0 | + | Chars | 790 | 133 | 5.9 | + | BIG Integers | 199848 | 103056 | 1.9 | + |--------------|----------------|----------------|----------------------| + + + + From 6bbf26bf289ec5899e77c071afc983ca9cd66947 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Tue, 14 Apr 2020 12:28:16 +0100 Subject: [PATCH 111/170] Respond to feedback --- CHANGELOG.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c10d55be..0a3577c4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,24 +27,25 @@ bumped version for float/double range bugfix # 1.2 1. Breaking change which mostly maintains backwards compatibility. -2. Faster by more x10 (depending on the type) - see below for benchmarks. -3. Passes a large number of random number test suites: +2. Support for monadic generators e.g. [mwc-random](https://hackage.haskell.org/package/mwc-random). +3. Monadic adapters for pure generators (providing a uniform monadic + interface to pure and monadic generators). +4. Faster by more x10 (depending on the type) - see below for benchmarks. +5. Passes a large number of random number test suites: * [dieharder](http://webhome.phy.duke.edu/~rgb/General/dieharder.php "venerable") * [TestU01 (SmallCrush, Crush, BigCrush)](http://simul.iro.umontreal.ca/testu01/tu01.html "venerable") * [PractRand](http://pracrand.sourceforge.net/ "active") * [gjrand](http://gjrand.sourceforge.net/ "active") - * [rademacher-fpl](https://gitlab.com/christoph-conrads/rademacher-fpl/-/tree/master "active") - * [gjrand](http://gjrand.sourceforge.net/ "active") * See [random-quality](https://github.com/tweag/random-quality) for details on how to do this yourself. -4. Better quality split as judged by these +6. Better quality split as judged by these [tests](https://www.cambridge.org/core/journals/journal-of-functional-programming/article/evaluation-of-splittable-pseudorandom-generators/3EBAA9F14939C5BB5560E32D1A132637). Again see [random-quality](https://github.com/tweag/random-quality) for details on how to do this yourself. -5. Unbiased generation of ranges. -6. Updated tests and benchmarks. -7. [Conntinuous integration](https://travis-ci.org/github/idontgetoutmuch/random). -8. Fully documented - for more details see the [haddock](https://htmlpreview.github.io/?https://github.com/idontgetoutmuch/random/blob/release-notes/docs/System-Random.html). +7. Unbiased generation of ranges. +8. Updated tests and benchmarks. +9. [Continuous integration](https://travis-ci.org/github/idontgetoutmuch/random). +10. Fully documented - for more details see the [haddock](https://htmlpreview.github.io/?https://github.com/idontgetoutmuch/random/blob/release-notes/docs/System-Random.html). ## Benchmarks From 5a7caf27a608c83b886fcde48c640287eae40a47 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Tue, 14 Apr 2020 16:08:52 +0200 Subject: [PATCH 112/170] Define bounds for library dependencies --- random.cabal | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/random.cabal b/random.cabal index 905e72098..b23acba5e 100644 --- a/random.cabal +++ b/random.cabal @@ -29,14 +29,14 @@ custom-setup library exposed-modules: System.Random + c-sources: cbits/CastFloatWord.cmm default-language: Haskell2010 ghc-options: -Wall build-depends: base >=4.10 && <5, - bytestring -any, - mtl -any, - splitmix -any - c-sources: cbits/CastFloatWord.cmm + bytestring >=0.10 && <0.11, + mtl >=2.2 && <2.3, + splitmix >=0.0.3 && <0.1 test-suite legacy type: exitcode-stdio-1.0 From a9a9f55738419f7f33e3bbf566d897e63988825b Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Tue, 14 Apr 2020 16:11:05 +0200 Subject: [PATCH 113/170] Set spec default-language --- random.cabal | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/random.cabal b/random.cabal index b23acba5e..31b38e76f 100644 --- a/random.cabal +++ b/random.cabal @@ -70,14 +70,15 @@ test-suite doctests unliftio -any test-suite spec - type: exitcode-stdio-1.0 - main-is: Spec.hs - hs-source-dirs: tests + type: exitcode-stdio-1.0 + main-is: Spec.hs + hs-source-dirs: tests other-modules: Spec.Range Spec.Run - ghc-options: -Wall + default-language: Haskell2010 + ghc-options: -Wall build-depends: base -any, random -any, From 2c3e0e57ef3086d3612dab7e5563200e1026d5e5 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Tue, 14 Apr 2020 16:34:22 +0200 Subject: [PATCH 114/170] Bound all dependencies --- random.cabal | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/random.cabal b/random.cabal index 31b38e76f..4baefb2cf 100644 --- a/random.cabal +++ b/random.cabal @@ -23,9 +23,9 @@ source-repository head custom-setup setup-depends: - base -any, - Cabal -any, - cabal-doctest >=1.0.6 + base >=4.10 && <5, + Cabal >=1.10 && <3.3, + cabal-doctest >=1.0.6 && <1.1 library exposed-modules: System.Random @@ -52,8 +52,8 @@ test-suite legacy default-language: Haskell2010 ghc-options: -with-rtsopts=-M4M build-depends: - base -any, - containers -any, + base >=4.10 && <5, + containers >=0.5 && <0.7, random -any test-suite doctests @@ -62,12 +62,12 @@ test-suite doctests hs-source-dirs: tests default-language: Haskell2010 build-depends: - base -any, - doctest >=0.15, - mwc-random -any, - primitive -any, + base >=4.10 && <5, + doctest >=0.15 && <0.17, + mwc-random >=0.13 && <0.15, + primitive >=0.6 && <0.8, random -any, - unliftio -any + unliftio >=0.2 && <0.3 test-suite spec type: exitcode-stdio-1.0 @@ -80,12 +80,12 @@ test-suite spec default-language: Haskell2010 ghc-options: -Wall build-depends: - base -any, + base >=4.10 && <5, random -any, - smallcheck -any, - tasty -any, - tasty-smallcheck -any, - tasty-expected-failure -any + smallcheck >=1.1 && <1.2, + tasty >=1.0 && <1.3, + tasty-smallcheck >=0.8 && <0.9, + tasty-expected-failure >=0.11 && <0.12 benchmark legacy-bench type: exitcode-stdio-1.0 @@ -95,8 +95,8 @@ benchmark legacy-bench default-language: Haskell2010 ghc-options: -Wall -O2 -threaded -rtsopts -with-rtsopts=-N build-depends: - base -any, + base >=4.10 && <5, random -any, rdtsc -any, - split -any, - time -any + split >=0.2 && <0.3, + time >=1.8 && <1.11 From 451ca399b16883501e2d8f255443e391cbfa34d8 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Tue, 14 Apr 2020 20:30:27 +0300 Subject: [PATCH 115/170] Handle compilation warnings in the legacy test suite --- random.cabal | 2 +- tests/Legacy/Random1283.hs | 18 ++++++---- tests/Legacy/RangeTest.hs | 68 +++++++++++++++++------------------ tests/Legacy/T7936.hs | 1 + tests/Legacy/TestRandomIOs.hs | 1 + tests/Legacy/TestRandomRs.hs | 3 +- 6 files changed, 51 insertions(+), 42 deletions(-) diff --git a/random.cabal b/random.cabal index 905e72098..a7cccffbf 100644 --- a/random.cabal +++ b/random.cabal @@ -50,7 +50,7 @@ test-suite legacy Legacy.RangeTest default-language: Haskell2010 - ghc-options: -with-rtsopts=-M4M + ghc-options: -with-rtsopts=-M4M -Wno-deprecations build-depends: base -any, containers -any, diff --git a/tests/Legacy/Random1283.hs b/tests/Legacy/Random1283.hs index c1aba38ef..53505b24b 100644 --- a/tests/Legacy/Random1283.hs +++ b/tests/Legacy/Random1283.hs @@ -1,21 +1,25 @@ module Legacy.Random1283 (main) where import Control.Concurrent -import Control.Monad hiding (empty) -import Data.Sequence (ViewL(..), empty, fromList, viewl, (<|), (|>), (><)) +import Control.Monad +import Data.Sequence (Seq, ViewL(..), empty, fromList, viewl, (<|), (|>), (><)) import System.Random -- This test +threads, samples :: Int threads = 4 samples = 5000 +main :: IO () main = loopTest threads samples +loopTest :: Int -> Int -> IO () loopTest t s = do isClean <- testRace t s - when (not isClean) $ putStrLn "race condition!" + unless isClean $ putStrLn "race condition!" +testRace :: Int -> Int -> IO Bool testRace t s = do ref <- liftM (take (t*s) . randoms) getStdGen iss <- threadRandoms t s @@ -25,11 +29,12 @@ threadRandoms :: Random a => Int -> Int -> IO [[a]] threadRandoms t s = do vs <- sequence $ replicate t $ do v <- newEmptyMVar - forkIO (sequence (replicate s randomIO) >>= putMVar v) + _ <- forkIO (sequence (replicate s randomIO) >>= putMVar v) return v mapM takeMVar vs -isInterleavingOf xs yss = iio xs (viewl $ fromList yss) EmptyL where +isInterleavingOf :: Eq a => [a] -> [[a]] -> Bool +isInterleavingOf xs' yss' = iio xs' (viewl $ fromList yss') EmptyL where iio (x:xs) ((y:ys) :< yss) zss | x /= y = iio (x:xs) (viewl yss) (viewl (fromViewL zss |> (y:ys))) | x == y = iio xs (viewl ((ys <| yss) >< fromViewL zss)) EmptyL @@ -37,6 +42,7 @@ isInterleavingOf xs yss = iio xs (viewl $ fromList yss) EmptyL where iio [] EmptyL EmptyL = True iio _ _ _ = False -fromViewL (EmptyL) = empty +fromViewL :: ViewL a -> Seq a +fromViewL EmptyL = empty fromViewL (x :< xs) = x <| xs diff --git a/tests/Legacy/RangeTest.hs b/tests/Legacy/RangeTest.hs index 7ed355591..c6b2e53af 100644 --- a/tests/Legacy/RangeTest.hs +++ b/tests/Legacy/RangeTest.hs @@ -6,23 +6,23 @@ import Control.Monad import System.Random import Data.Int import Data.Word -import Data.Bits import Foreign.C.Types -- Take many measurements and record the max/min/average random values. -approxBounds :: (RandomGen g, Random a, Ord a, Num a) => - (g -> (a,g)) -> Int -> a -> (a,a) -> g -> ((a,a,a),g) +approxBounds :: + (RandomGen g, Random a, Ord a, Num a) => + (g -> (a,g)) -> Int -> a -> (a,a) -> g -> ((a,a,a),g) -- Here we do a little hack to essentiall pass in the type in the last argument: -approxBounds nxt iters unused (explo,exphi) initrng = - if False +approxBounds nxt iters unused (explo,exphi) initrng = + if False then ((unused,unused,unused),undefined) -- else loop initrng iters 100 (-100) 0 -- Oops, can't use minBound/maxBound here. - else loop initrng iters exphi explo 0 -- Oops, can't use minBound/maxBound here. - where - loop rng 0 mn mx sum = ((mn,mx,sum),rng) - loop rng n mn mx sum = - case nxt rng of - (x, rng') -> loop rng' (n-1) (min x mn) (max x mx) (x+sum) + else loop initrng iters exphi explo 0 + where + loop rng 0 mn mx sum' = ((mn,mx,sum'),rng) + loop rng n mn mx sum' = + case nxt rng of + (x, rng') -> loop rng' (n-1) (min x mn) (max x mx) (x+sum') -- We check that: @@ -30,34 +30,34 @@ approxBounds nxt iters unused (explo,exphi) initrng = -- (2) we get "close" to the bounds -- The with (2) is that we do enough trials to ensure that we can at -- least hit the 90% mark. -checkBounds:: (Real a, Show a, Ord a) => - String -> (Bool, a, a) -> ((a,a) -> StdGen -> ((a, a, t), StdGen)) -> IO () -checkBounds msg (exclusive,lo,hi) fun = - -- (lo,hi) is [inclusive,exclusive) - do putStr$ msg --- ++ ", expected range " ++ show (lo,hi) - ++ ": " - (mn,mx,sum) <- getStdRandom (fun (lo,hi)) - when (mn < lo)$ error$ "broke lower bound: " ++ show mn - when (mx > hi) $ error$ "broke upper bound: " ++ show mx - when (exclusive && mx >= hi)$ error$ "hit upper bound: " ++ show mx - - let epsilon = 0.1 * (toRational hi - toRational lo) - - when (toRational (hi - mx) > epsilon)$ error$ "didn't get close enough to upper bound: "++ show mx - when (toRational (mn - lo) > epsilon)$ error$ "didn't get close enough to lower bound: "++ show mn - putStrLn "Passed" +checkBounds :: + (Real a, Show a, Ord a) => + String -> (Bool, a, a) -> ((a,a) -> StdGen -> ((a, a, t), StdGen)) -> IO () +checkBounds msg (exclusive,lo,hi) fun = do + -- (lo,hi) is [inclusive,exclusive) + putStr $ msg ++ ": " + (mn,mx,_) <- getStdRandom (fun (lo,hi)) + when (mn < lo) $ error $ "broke lower bound: " ++ show mn + when (mx > hi) $ error $ "broke upper bound: " ++ show mx + when (exclusive && mx >= hi)$ error$ "hit upper bound: " ++ show mx + + let epsilon = 0.1 * (toRational hi - toRational lo) + + when (toRational (hi - mx) > epsilon) $ error $ "didn't get close enough to upper bound: "++ show mx + when (toRational (mn - lo) > epsilon) $ error $ "didn't get close enough to lower bound: "++ show mn + putStrLn "Passed" boundedRange :: (Num a, Bounded a) => (Bool, a, a) boundedRange = ( False, minBound, maxBound ) +trials :: Int trials = 5000 -- Keep in mind here that on some architectures (e.g. ARM) CChar, CWchar, and CSigAtomic -- are unsigned - -main = - do +main :: IO () +main = + do checkBounds "Int" boundedRange (approxBounds random trials (undefined::Int)) checkBounds "Integer" (False, fromIntegral (minBound::Int), fromIntegral (maxBound::Int)) (approxBounds random trials (undefined::Integer)) @@ -95,9 +95,9 @@ main = -- Then check all the range-restricted versions: checkBounds "Int R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int)) - checkBounds "Integer R" (False,-100000000000000000000,100000000000000000000) - (approxBounds (randomR (-100000000000000000000,100000000000000000000)) - trials (undefined::Integer)) + checkBounds "Integer R" + (False,-100000000000000000000,100000000000000000000) + (approxBounds (randomR (-100000000000000000000,100000000000000000000)) trials (undefined::Integer)) checkBounds "Int8 R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined::Int8)) checkBounds "Int8 Rsmall" (False,-50,50) (approxBounds (randomR (-50,50)) trials (undefined::Int8)) checkBounds "Int8 Rmini" (False,3,4) (approxBounds (randomR (3,4)) trials (undefined::Int8)) diff --git a/tests/Legacy/T7936.hs b/tests/Legacy/T7936.hs index 0ed0d109b..f351634be 100644 --- a/tests/Legacy/T7936.hs +++ b/tests/Legacy/T7936.hs @@ -11,4 +11,5 @@ module Legacy.T7936 where import System.Random (newStdGen) import Control.Monad (replicateM_) +main :: IO () main = replicateM_ 100000 newStdGen diff --git a/tests/Legacy/TestRandomIOs.hs b/tests/Legacy/TestRandomIOs.hs index b07a993f0..cc81477b8 100644 --- a/tests/Legacy/TestRandomIOs.hs +++ b/tests/Legacy/TestRandomIOs.hs @@ -15,6 +15,7 @@ import System.Random (randomIO) -- the last one. -- Should use less than 1Mb of heap space, or we are generating a list of -- unevaluated thunks. +main :: IO () main = do rs <- replicateM 5000 randomIO :: IO [Int] print $ last rs diff --git a/tests/Legacy/TestRandomRs.hs b/tests/Legacy/TestRandomRs.hs index 8a786b5c2..a85431157 100644 --- a/tests/Legacy/TestRandomRs.hs +++ b/tests/Legacy/TestRandomRs.hs @@ -12,11 +12,12 @@ module Legacy.TestRandomRs where -import Control.Monad (liftM, replicateM) +import Control.Monad (liftM) import System.Random (randomRs, getStdGen) -- Return the five-thousandth random number: -- Should run in constant space (< 1Mb heap). +main :: IO () main = do n <- (last . take 5000 . randomRs (0, 1000000)) `liftM` getStdGen print (n::Integer) From 5ef660b3df2b47828e029b8fd9c047b4fd735df3 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Tue, 14 Apr 2020 21:00:16 +0300 Subject: [PATCH 116/170] Rename `genShortByteStringWith` to `genShortByteStringIO` for consistency --- System/Random.hs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 6eb9ccd7e..2f08af0ff 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -368,7 +368,7 @@ module System.Random , Random(..) -- * Generators for sequences of bytes - , genShortByteStringWith + , genShortByteStringIO , genShortByteStringST , uniformByteString , genByteString @@ -507,7 +507,7 @@ class RandomGen g where -- @since 1.2 genShortByteString :: Int -> g -> (ShortByteString, g) genShortByteString n g = - unsafePerformIO $ runGenStateT g (genShortByteStringWith n . uniformWord64) + unsafePerformIO $ runGenStateT g (genShortByteStringIO n . uniformWord64) {-# INLINE genShortByteString #-} -- | Yields the range of values returned by 'next'. @@ -611,7 +611,7 @@ class Monad m => MonadRandom g s m | g m -> s where -- @since 1.2 uniformShortByteString :: Int -> g s -> m ShortByteString default uniformShortByteString :: MonadIO m => Int -> g s -> m ShortByteString - uniformShortByteString n = genShortByteStringWith n . uniformWord64 + uniformShortByteString n = genShortByteStringIO n . uniformWord64 {-# INLINE uniformShortByteString #-} @@ -674,8 +674,8 @@ data MBA s = MBA (MutableByteArray# s) -- calls. -- -- @since 1.2 -genShortByteStringWith :: MonadIO m => Int -> m Word64 -> m ShortByteString -genShortByteStringWith n0 gen64 = do +genShortByteStringIO :: MonadIO m => Int -> m Word64 -> m ShortByteString +genShortByteStringIO n0 gen64 = do let !n@(I# n#) = max 0 n0 (n64, nrem64) = n `quotRem` 8 MBA mba# <- @@ -709,14 +709,14 @@ genShortByteStringWith n0 gen64 = do IO $ \s# -> case unsafeFreezeByteArray# mba# s# of (# s'#, ba# #) -> (# s'#, SBS ba# #) -{-# INLINE genShortByteStringWith #-} +{-# INLINE genShortByteStringIO #-} --- | Same as 'genShortByteStringWith', but runs in 'ST'. +-- | Same as 'genShortByteStringIO', but runs in 'ST'. -- -- @since 1.2 genShortByteStringST :: Int -> ST s Word64 -> ST s ShortByteString genShortByteStringST n action = - unsafeIOToST (genShortByteStringWith n (unsafeSTToIO action)) + unsafeIOToST (genShortByteStringIO n (unsafeSTToIO action)) pinnedByteArrayToByteString :: ByteArray# -> ByteString pinnedByteArrayToByteString ba# = From 96acf32d6c9971c8c15b045eb7093bbee9f60a07 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 10 Apr 2020 10:13:42 +0200 Subject: [PATCH 117/170] Remove references to PrimGen, MutGen --- System/Random.hs | 84 +++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 44 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 2f08af0ff..7f16aac7a 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -42,8 +42,8 @@ -- [Monadic pseudo-random number generators] 'MonadRandom' is an interface to -- monadic pseudo-random number generators. -- --- [Monadic adapters] 'PureGen', 'PrimGen' and 'MutGen' turn a 'RandomGen' --- instance into a 'MonadRandom' instance. +-- [Monadic adapters] 'PureGen', 'AtomicGen', 'IOGen' and 'STGen' turn a +-- 'RandomGen' instance into a 'MonadRandom' instance. -- -- [Drawing from a range] 'UniformRange' is used to generate a value of a -- datatype uniformly within a range. @@ -109,36 +109,43 @@ -- -- [Monadic pseudo-random number generators] These generators mutate their own -- state as they produce random values. They generally live in 'ST' or 'IO' --- or some transformer that implements 'PrimMonad'. 'MonadRandom' defines +-- or some transformer that implements @PrimMonad@. 'MonadRandom' defines -- the interface for monadic pseudo-random number generators. -- -- Pure pseudo-random number generators can be used in monadic code via the --- adapters 'PureGen', 'PrimGen' and 'MutGen'. +-- adapters 'PureGen', 'AtomicGen', 'IOGen' and 'STGen'. -- -- * 'PureGen' can be used in any state monad. With strict 'StateT' there is --- even no performance overhead when compared to the 'RandomGen' directly. --- But it is not safe to use it in presence of exceptions and resource --- allocation that requires cleanup. +-- no performance overhead compared to using the 'RandomGen' instance +-- directly. 'PureGen' is /not/ safe to use in the presence of exceptions +-- and concurrency. -- --- * 'PrimGen' can be used in any 'PrimMonad' if the pseudo-random number --- generator is also an instance of 'Prim'. It will perform much faster --- than 'MutGen', but it can't be used in a concurrent setting. +-- * 'AtomicGen' is safe in the presence of exceptions and concurrency since +-- it performs all actions atomically. -- --- * 'MutGen' can be used in any 'PrimMonad' (this includes 'ST' and 'IO') and --- can safely be shared between threads. +-- * 'IOGen' is a wrapper around an 'IORef' that holds a pure generator. +-- 'IOGen' is /not/ safe to use in the presence of exceptions and +-- concurrency. +-- +-- * 'STGen' is a wrapper around an 'STRef' that holds a pure generator. +-- 'STGen' is /not/ safe to use in the presence of exceptions and +-- concurrency. -- -- When to use which? -- -- * Use 'PureGen' if your computation does not have side effects and results -- in a pure function. -- --- * Use 'PrimGen' if the pseudo-random number generator implements 'Prim' --- class and random value generation is intermixed with 'IO' or 'ST'. +-- * Use 'AtomicGen' if the pseudo-random number generator must be shared +-- between threads or if exception safety is a requirement. +-- +-- * Use 'IOGen' if operating in a 'MonadIO' context, exception safety is not +-- a concern and the pseudo-random number generator is not shared between +-- threads. -- --- * Otherwise use 'MutGen'. Whenever a 'MutGen' is shared between threads, --- make sure there is not much contention for it, otherwise performance --- will suffer. For parallel random value generation it is best to split --- the generator and use a single 'PureGen' or 'PrimGen' per thread. +-- * Use 'STGen' if operating in an 'ST' context, exception safety is not +-- a concern and the pseudo-random number generator is not shared between +-- threads. -- -- = How to generate random values in monadic code -- @@ -161,9 +168,8 @@ -- [2,3,6,6,4,4,3,1,5,4] -- -- Given a /pure/ pseudo-random number generator, you can run it in an 'IO' or --- 'ST' context using 'runPrimGenIO' or 'runPrimGenST' and their variants, --- respectively. If the pseudo-random number generator does not implement --- 'Prim', you can also use 'runMutGenIO' or 'runMutGenST' and their variants. +-- 'ST' context by first applying a monadic adapter like 'AtomicGen', 'IOGen' +-- or 'STGen' and then running it with 'runGenM'. -- -- >>> let pureGen = mkStdGen 42 -- >>> runGenM_ (IOGen pureGen) (rolls 10) :: IO [Word8] @@ -193,10 +199,8 @@ -- modulus, implement 'next' and 'genRange'. See below for more details. -- -- * If the pseudo-random number generator is splittable, implement 'split'. --- --- Additionally, implement 'Prim' for the pseudo-random number generator if --- possible. This allows users to use the fast 'MutGen' adapter with the --- pseudo-random number generator. +-- If there is no suitable implementation, 'split' should fail with a +-- helpful error message. -- -- == How to implement 'RandomGen' for a pseudo-random number generator with power-of-2 modulus -- @@ -315,8 +319,7 @@ module System.Random ( - -- * Random number generators - + -- * Pseudo-random number generator interfaces RandomGen(..) , MonadRandom(..) , Frozen(..) @@ -324,12 +327,12 @@ module System.Random , runGenM_ , RandomGenM(..) , splitRandomGenM - -- ** Standard random number generators + -- ** Standard pseudo-random number generator , StdGen , mkStdGen - -- * Stateful interface for pure generators - -- ** Based on StateT + -- * Monadic adapters for pure pseudo-random number generators + -- ** Pure adapter , PureGen , splitGen , genRandom @@ -338,7 +341,7 @@ module System.Random , runGenStateT , runGenStateT_ , runPureGenST - -- ** Mutable generators + -- ** Mutable adapters -- *** AtomicGen , AtomicGen , applyAtomicGen @@ -351,23 +354,21 @@ module System.Random , runSTGen , runSTGen_ - -- ** The global random number generator - + -- ** The global pseudo-random number generator -- $globalrng - , getStdRandom , getStdGen , setStdGen , newStdGen - -- * Random values of various types + -- * Pseudo-random values of various types -- $uniform , Uniform(..) , uniformListM , UniformRange(..) , Random(..) - -- * Generators for sequences of bytes + -- * Generators for sequences of pseudo-random bytes , genShortByteStringIO , genShortByteStringST , uniformByteString @@ -439,7 +440,7 @@ import GHC.IO (IO(..)) -- | 'RandomGen' is an interface to pure pseudo-random number generators. -- --- 'StdGen' is the default 'RandomGen' instance provided by this library. +-- 'StdGen' is the standard 'RandomGen' instance provided by this library. {-# DEPRECATED next "No longer used" #-} {-# DEPRECATED genRange "No longer used" #-} class RandomGen g where @@ -955,7 +956,7 @@ runSTGen g action = unSTGen <$> runST (runGenM (STGen g) action) runSTGen_ :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> a runSTGen_ g action = fst $ runSTGen g action --- | The default pseudo-random number generator. +-- | The standard pseudo-random number generator. type StdGen = SM.SMGen instance RandomGen StdGen where @@ -964,12 +965,7 @@ instance RandomGen StdGen where genWord64 = SM.nextWord64 split = SM.splitSMGen - -{- | -The function 'mkStdGen' provides an alternative way of producing an initial -generator, by mapping an 'Int' into a generator. Again, distinct arguments -should be likely to produce distinct generators. --} +-- | Constructs a 'StdGen' deterministically. mkStdGen :: Int -> StdGen mkStdGen s = SM.mkSMGen $ fromIntegral s From 533ded7799f9ac01aefa54ce1b6bf33d1648c51c Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 10 Apr 2020 10:40:56 +0200 Subject: [PATCH 118/170] Make StdGen opaque --- System/Random.hs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 7f16aac7a..2fda08b4d 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -645,7 +645,7 @@ instance RandomGen r => RandomGenM STGen r s (ST s) where -- -- >>> import Data.Int (Int8) -- >>> runGenM (IOGen (mkStdGen 217)) (`uniformListM` 5) :: IO ([Int8], Frozen (IOGen StdGen)) --- ([-74,37,-50,-2,3],IOGen {unIOGen = SMGen 4273268533320920145 15251669095119325999}) +-- ([-74,37,-50,-2,3],IOGen {unIOGen = StdGen {unStdGen = SMGen 4273268533320920145 15251669095119325999}}) -- -- @since 1.2 runGenM :: MonadRandom g s m => Frozen g -> (g s -> m a) -> m (a, Frozen g) @@ -957,17 +957,18 @@ runSTGen_ :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> a runSTGen_ g action = fst $ runSTGen g action -- | The standard pseudo-random number generator. -type StdGen = SM.SMGen +newtype StdGen = StdGen { unStdGen :: SM.SMGen } + deriving Show instance RandomGen StdGen where - next = SM.nextInt - genWord32 = SM.nextWord32 - genWord64 = SM.nextWord64 - split = SM.splitSMGen + next = second StdGen . SM.nextInt . unStdGen + genWord32 = second StdGen . SM.nextWord32 . unStdGen + genWord64 = second StdGen . SM.nextWord64 . unStdGen + split g = let (g1, g2) = SM.splitSMGen $ unStdGen g in (StdGen g1, StdGen g2) -- | Constructs a 'StdGen' deterministically. mkStdGen :: Int -> StdGen -mkStdGen s = SM.mkSMGen $ fromIntegral s +mkStdGen = StdGen . SM.mkSMGen . fromIntegral -- $uniform @@ -1628,7 +1629,7 @@ getStdGen :: IO StdGen getStdGen = readIORef theStdGen theStdGen :: IORef StdGen -theStdGen = unsafePerformIO $ SM.initSMGen >>= newIORef +theStdGen = unsafePerformIO $ SM.initSMGen >>= newIORef . StdGen {-# NOINLINE theStdGen #-} -- |Applies 'split' to the current global random generator, From 9cb752d1fd779b1062970eae4610f200a91a0a93 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 10 Apr 2020 11:10:18 +0200 Subject: [PATCH 119/170] No need to use the splitmix fork anymore --- stack-coveralls.yaml | 2 -- stack-old.yaml | 3 +-- stack.yaml | 4 +--- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/stack-coveralls.yaml b/stack-coveralls.yaml index 0103119c5..5575c2ffb 100644 --- a/stack-coveralls.yaml +++ b/stack-coveralls.yaml @@ -3,8 +3,6 @@ packages: - . extra-deps: - rdtsc-1.3.0.1@sha256:0a6e8dc715ba82ad72c7e2b1c2f468999559bec059d50540719a80b00dcc4e66,1557 -- git: https://github.com/lehins/splitmix.git - commit: bb92e025a0d0a0353e2d6369191fecec8e8c99ad flags: splitmix: random: false diff --git a/stack-old.yaml b/stack-old.yaml index f689cdc66..750067a74 100644 --- a/stack-old.yaml +++ b/stack-old.yaml @@ -50,8 +50,7 @@ extra-deps: # In addition, the doctests require 'SMGen' from the splitmix package to # implement 'Prim'. So far, this is only the case on lehin's fork, so we use # that. -- git: https://github.com/lehins/splitmix.git - commit: bb92e025a0d0a0353e2d6369191fecec8e8c99ad +- splitmix-0.0.4@sha256:fb9bb8b54a2e76c8a021fe5c4c3798047e1f60e168379a1f80693047fe00ad0e,4813 # Not contained in all snapshots we want to test against - doctest-0.16.2@sha256:2f96e9bbe9aee11b47453c82c24b3dc76cdbb8a2a7c984dfd60b4906d08adf68,6942 - cabal-doctest-1.0.8@sha256:34dff6369d417df2699af4e15f06bc181d495eca9c51efde173deae2053c197c,1491 diff --git a/stack.yaml b/stack.yaml index ca5be157b..572921a65 100644 --- a/stack.yaml +++ b/stack.yaml @@ -34,9 +34,7 @@ packages: # Dependency packages to be pulled from upstream that are not in the resolver. # These entries can reference officially published versions as well as # forks / in-progress versions pinned to a git hash. For example: -extra-deps: -- git: https://github.com/lehins/splitmix.git - commit: bb92e025a0d0a0353e2d6369191fecec8e8c99ad +extra-deps: [] # Override default flag values for local packages and extra-deps flags: From 59bce2b933ffe16e623db361d77371cd987318ba Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 10 Apr 2020 16:00:52 +0200 Subject: [PATCH 120/170] Overhaul Haddocks for UniformRange and Uniform --- System/Random.hs | 60 +++++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 2fda08b4d..647bc7c58 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -46,7 +46,7 @@ -- 'RandomGen' instance into a 'MonadRandom' instance. -- -- [Drawing from a range] 'UniformRange' is used to generate a value of a --- datatype uniformly within a range. +-- datatype uniformly within an inclusive range. -- -- This library provides instances of 'UniformRange' for many common -- numeric datatypes. @@ -956,7 +956,9 @@ runSTGen g action = unSTGen <$> runST (runGenM (STGen g) action) runSTGen_ :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> a runSTGen_ g action = fst $ runSTGen g action --- | The standard pseudo-random number generator. +-- | The standard pseudo-random number generator. Uses the SplitMix +-- implementation provided by the +-- [splitmix](https://hackage.haskell.org/package/splitmix) package. newtype StdGen = StdGen { unStdGen :: SM.SMGen } deriving Show @@ -972,43 +974,44 @@ mkStdGen = StdGen . SM.mkSMGen . fromIntegral -- $uniform +-- This library provides two type classes to generate pseudo-random values: -- --- @random@ has two type classes for generation of random numbers: --- 'Uniform' and 'UniformRange'. One for generating every possible --- value and another for generating every value in range. In other --- libraries this functionality frequently bundled into single type --- class but here we have two type classes because there're types --- which could have instance for one type class but not the other. +-- * 'UniformRange' is used to generate a value of a datatype uniformly +-- within an inclusive range. +-- * 'Uniform' is used to generate a value of a datatype uniformly over all +-- possible values of that datatype. -- --- For example: 'Integer', 'Float', 'Double' have instance for --- @UniformRange@ but there's no way to define @Uniform@. +-- Types may have instances for both or just one of 'UniformRange' and +-- 'Uniform'. A few examples illustrate this: -- --- Conversely there're types where @Uniform@ instance is possible --- while @UniformRange@ is not. One example is tuples: @(a,b)@. While --- @Uniform@ instance is straightforward it's not clear how to define --- @UniformRange@. We could try to generate values that @a <= x <= b@ --- But to do that we need to know number of elements of tuple's second --- type parameter @b@ which we don't have. --- --- Or type could have no order at all. Take for example --- angle. Defining @Uniform@ instance is again straghtforward: just --- generate value in @[0,2π)@ range. But for any two pair of angles --- there're two ranges: clockwise and counterclockwise. +-- * 'Int', 'Word16' and 'Bool' are instances of both 'UniformRange' and +-- 'Uniform'. +-- * 'Integer', 'Float' and 'Double' each have an instance for 'UniformRange' +-- but no 'Uniform' instance. +-- * It is trivial to construct a @Uniform (a, b)@ instance given +-- @Uniform a@ and @Uniform b@ (and this library provides this tuple +-- instance). +-- * On the other hand, there is no correct way to construct a +-- @UniformRange (a, b)@ instance based on just @UniformRange a@ and +-- @UniformRange b@. --- | Generate every possible value for data type with equal probability. +-- | Generates a value uniformly distributed over all possible values of that +-- datatype. -- -- @since 1.2 class Uniform a where uniform :: MonadRandom g s m => g s -> m a --- | Generate every value in provided inclusive range with equal --- probability. So @uniformR (1,4)@ should generate values from set --- @[1,2,3,4]@. Inclusive range is used to allow to express any --- interval for fixed-size ints, enumerations etc. +-- | Generates a value uniformly distributed over the provided inclusive range. +-- +-- For example, @uniformR (1,4)@ should generate values uniformly from the set +-- @[1,2,3,4]@. -- --- Additionally in order to make function always defined order of --- elements in range shouldn't matter and following law should hold: +-- The API uses an inclusive range so any range can be expressed, even when +-- using fixed-size ints, enumerations etc. +-- +-- The following law should hold to make the function always defined: -- -- > uniformR (a,b) = uniform (b,a) -- @@ -1016,7 +1019,6 @@ class Uniform a where class UniformRange a where uniformR :: MonadRandom g s m => (a, a) -> g s -> m a - {- | With a source of random number supply in hand, the 'Random' class allows the programmer to extract random values of a variety of types. From 636df077b17a782044d36d4a1ceeb4555da9beb8 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 10 Apr 2020 16:01:12 +0200 Subject: [PATCH 121/170] Provide Uniform instances for tuples up to n=6 --- System/Random.hs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/System/Random.hs b/System/Random.hs index 647bc7c58..b833ea4ea 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1611,6 +1611,50 @@ unsignedBitmaskWithRejectionM genUniform range gen = go else pure x' {-# INLINE unsignedBitmaskWithRejectionM #-} +------------------------------------------------------------------------------- +-- 'Uniform' instances for tuples +------------------------------------------------------------------------------- + +instance (Uniform a, Uniform b) => Uniform (a, b) where + uniform g = do + a <- uniform g + b <- uniform g + return (a, b) + +instance (Uniform a, Uniform b, Uniform c) => Uniform (a, b, c) where + uniform g = do + a <- uniform g + b <- uniform g + c <- uniform g + return (a, b, c) + +instance (Uniform a, Uniform b, Uniform c, Uniform d) => Uniform (a, b, c, d) where + uniform g = do + a <- uniform g + b <- uniform g + c <- uniform g + d <- uniform g + return (a, b, c, d) + +instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e) => Uniform (a, b, c, d, e) where + uniform g = do + a <- uniform g + b <- uniform g + c <- uniform g + d <- uniform g + e <- uniform g + return (a, b, c, d, e) + +instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e, Uniform f) => Uniform (a, b, c, d, e, f) where + uniform g = do + a <- uniform g + b <- uniform g + c <- uniform g + d <- uniform g + e <- uniform g + f <- uniform g + return (a, b, c, d, e, f) + -- The global random number generator {- $globalrng #globalrng# From f57f1bb107d536cdffa24d8345cc1da1a1146838 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 10 Apr 2020 16:06:55 +0200 Subject: [PATCH 122/170] random -> pseudo-random --- System/Random.hs | 113 ++++++++++++++++++++++++----------------------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index b833ea4ea..0ae8dd92a 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -96,10 +96,10 @@ -- -- Pseudo-random number generators come in two flavours: /pure/ and /monadic/. -- --- [Pure pseudo-random number generators] These generators produce a new random --- value together with a new instance of the pseudo-random number --- generator. 'RandomGen' defines the interface for pure pseudo-random --- number generators. +-- [Pure pseudo-random number generators] These generators produce a new +-- pseudo-random value together with a new instance of the pseudo-random +-- number generator. 'RandomGen' defines the interface for pure +-- pseudo-random number generators. -- -- Pure pseudo-random number generators should implement 'split' if they -- are /splittable/, that is, if there is an efficient method to turn one @@ -108,9 +108,9 @@ -- some background on splittable pseudo-random generators. -- -- [Monadic pseudo-random number generators] These generators mutate their own --- state as they produce random values. They generally live in 'ST' or 'IO' --- or some transformer that implements @PrimMonad@. 'MonadRandom' defines --- the interface for monadic pseudo-random number generators. +-- state as they produce pseudo-random values. They generally live in 'ST' +-- or 'IO' or some transformer that implements @PrimMonad@. 'MonadRandom' +-- defines the interface for monadic pseudo-random number generators. -- -- Pure pseudo-random number generators can be used in monadic code via the -- adapters 'PureGen', 'AtomicGen', 'IOGen' and 'STGen'. @@ -147,13 +147,13 @@ -- a concern and the pseudo-random number generator is not shared between -- threads. -- --- = How to generate random values in monadic code +-- = How to generate pseudo-random values in monadic code -- -- In monadic code, use the relevant 'Uniform' and 'UniformRange' instances to --- generate random values via 'uniform' and 'uniformR', respectively. +-- generate pseudo-random values via 'uniform' and 'uniformR', respectively. -- --- As an example, @rolls@ generates @n@ random values of @Word8@ in the range --- @[1, 6]@. +-- As an example, @rolls@ generates @n@ pseudo-random values of @Word8@ in the +-- range @[1, 6]@. -- -- >>> :{ -- let rolls :: MonadRandom g s m => Int -> g s -> m [Word8] @@ -175,11 +175,11 @@ -- >>> runGenM_ (IOGen pureGen) (rolls 10) :: IO [Word8] -- [1,1,3,2,4,5,3,4,6,2] -- --- = How to generate random values in pure code +-- = How to generate pseudo-random values in pure code -- --- In pure code, use 'runGenState' and its variants to extract the pure random --- value from a monadic computation based on a pure pseudo-random number --- generator. +-- In pure code, use 'runGenState' and its variants to extract the pure +-- pseudo-random value from a monadic computation based on a pure pseudo-random +-- number generator. -- -- >>> let pureGen = mkStdGen 42 -- >>> runGenState_ pureGen (rolls 10) :: [Word8] @@ -264,9 +264,9 @@ -- Pseudo-random number generators without a power-of-2 modulus perform -- /significantly worse/ than pseudo-random number generators with a power-of-2 -- modulus with this library. This is because most functionality in this --- library is based on generating and transforming uniformly random machine --- words, and generating uniformly random machine words using a pseudo-random --- number generator without a power-of-2 modulus is expensive. +-- library is based on generating and transforming uniformly pseudo-random +-- machine words, and generating uniformly pseudo-random machine words using a +-- pseudo-random number generator without a power-of-2 modulus is expensive. -- -- The pseudo-random number generator from -- natively @@ -299,7 +299,7 @@ -- = How to implement 'MonadRandom' -- -- Typically, a monadic pseudo-random number generator has facilities to save --- and restore its internal state in addition to generating random +-- and restore its internal state in addition to generating pseudo-random -- pseudo-random numbers. -- -- Here is an example instance for the monadic pseudo-random number generator @@ -503,7 +503,7 @@ class RandomGen g where genWord64R m g = runGenState g (unsignedBitmaskWithRejectionM uniformWord64 m) -- | @genShortByteString n g@ returns a 'ShortByteString' of length @n@ - -- filled with random bytes. + -- filled with pseudo-random bytes. -- -- @since 1.2 genShortByteString :: Int -> g -> (ShortByteString, g) @@ -607,7 +607,7 @@ class Monad m => MonadRandom g s m | g m -> s where pure (unsafeShiftL (fromIntegral h32) 32 .|. fromIntegral l32) -- | @uniformShortByteString n g@ generates a 'ShortByteString' of length @n@ - -- filled with random bytes. + -- filled with pseudo-random bytes. -- -- @since 1.2 uniformShortByteString :: Int -> g s -> m ShortByteString @@ -661,7 +661,7 @@ runGenM fg action = do runGenM_ :: MonadRandom g s m => Frozen g -> (g s -> m a) -> m a runGenM_ fg action = fst <$> runGenM fg action --- | Generates a list of random values. +-- | Generates a list of pseudo-random values. -- -- @since 1.2 uniformListM :: (MonadRandom g s m, Uniform a) => g s -> Int -> m [a] @@ -670,9 +670,9 @@ uniformListM gen n = replicateM n (uniform gen) data MBA s = MBA (MutableByteArray# s) --- | Efficiently generates a sequence of random bytes in a platform independent --- manner. The allocated memory is be pinned, so it is safe to use with FFI --- calls. +-- | Efficiently generates a sequence of pseudo-random bytes in a platform +-- independent manner. The allocated memory is be pinned, so it is safe to use +-- with FFI calls. -- -- @since 1.2 genShortByteStringIO :: MonadIO m => Int -> m Word64 -> m ShortByteString @@ -729,7 +729,7 @@ pinnedByteArrayToForeignPtr ba# = ForeignPtr (byteArrayContents# ba#) (PlainPtr (unsafeCoerce# ba#)) {-# INLINE pinnedByteArrayToForeignPtr #-} --- | Generates a random 'ByteString' of the specified size. +-- | Generates a pseudo-random 'ByteString' of the specified size. -- -- @since 1.2 uniformByteString :: MonadRandom g s m => Int -> g s -> m ByteString @@ -776,7 +776,7 @@ instance (RandomGen g, MonadState g m) => MonadRandom (PureGen g) g m where uniformWord64 _ = state genWord64 uniformShortByteString n _ = state (genShortByteString n) --- | Generates a random value in a state monad. +-- | Generates a pseudo-random value in a state monad. -- -- @since 1.2 genRandom :: (RandomGen g, Random a, MonadState g m) => PureGen g g -> m a @@ -797,7 +797,8 @@ runGenState :: RandomGen g => g -> (PureGen g g -> State g a) -> (a, g) runGenState g f = runState (f PureGenI) g -- | Runs a monadic generating action in the `State` monad using a pure --- pseudo-random number generator. Returns only the resulting random value. +-- pseudo-random number generator. Returns only the resulting pseudo-random +-- value. -- -- @since 1.2 runGenState_ :: RandomGen g => g -> (PureGen g g -> State g a) -> a @@ -811,7 +812,8 @@ runGenStateT :: RandomGen g => g -> (PureGen g g -> StateT g m a) -> m (a, g) runGenStateT g f = runStateT (f PureGenI) g -- | Runs a monadic generating action in the `StateT` monad using a pure --- pseudo-random number generator. Returns only the resulting random value. +-- pseudo-random number generator. Returns only the resulting pseudo-random +-- value. -- -- @since 1.2 runGenStateT_ :: (RandomGen g, Functor f) => g -> (PureGen g g -> StateT g f a) -> f a @@ -860,10 +862,10 @@ applyAtomicGen op (AtomicGenI gVar) = -- not being used with `IOGen`. Which also means that it is not safe in a -- concurrent setting. -- --- Both `IOGen` and `AtomicGen` are necessary when generation of random values --- happens in `IO` and especially when dealing with exception handling and --- resource allocation, which is where `StateT` should never be used. For --- example writing a random number of bytes into a temporary file: +-- Both `IOGen` and `AtomicGen` are necessary when generation of pseudo-random +-- values happens in `IO` and especially when dealing with exception handling +-- and resource allocation, which is where `StateT` should never be used. For +-- example writing a pseudo-random number of bytes into a temporary file: -- -- >>> import UnliftIO.Temporary (withSystemTempFile) -- >>> import Data.ByteString (hPutStr) @@ -950,7 +952,8 @@ runSTGen :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> (a, g) runSTGen g action = unSTGen <$> runST (runGenM (STGen g) action) -- | Runs a monadic generating action in the `ST` monad using a pure --- pseudo-random number generator. Returns only the resulting random value. +-- pseudo-random number generator. Returns only the resulting pseudo-random +-- value. -- -- @since 1.2 runSTGen_ :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> a @@ -1020,8 +1023,8 @@ class UniformRange a where uniformR :: MonadRandom g s m => (a, a) -> g s -> m a {- | -With a source of random number supply in hand, the 'Random' class allows the -programmer to extract random values of a variety of types. +With a source of pseudo-random number supply in hand, the 'Random' class allows +the programmer to extract pseudo-random values of a variety of types. Minimal complete definition: 'randomR' and 'random'. @@ -1030,9 +1033,9 @@ Minimal complete definition: 'randomR' and 'random'. {-# DEPRECATED randomIO "In favor of `uniformR`" #-} class Random a where - -- | Takes a range /(lo,hi)/ and a random number generator - -- /g/, and returns a random value uniformly distributed over the closed - -- interval /[lo,hi]/, together with a new generator. It is unspecified + -- | Takes a range /(lo,hi)/ and a pseudo-random number generator + -- /g/, and returns a pseudo-random value uniformly distributed over the + -- closed interval /[lo,hi]/, together with a new generator. It is unspecified -- what happens if /lo>hi/. For continuous types there is no requirement -- that the values /lo/ and /hi/ are ever produced, but they may be, -- depending on the implementation and the interval. @@ -1060,28 +1063,28 @@ class Random a where -- randomM = uniform -- | Plural variant of 'randomR', producing an infinite list of - -- random values instead of returning a new generator. + -- pseudo-random values instead of returning a new generator. {-# INLINE randomRs #-} randomRs :: RandomGen g => (a,a) -> g -> [a] randomRs ival g = build (\cons _nil -> buildRandoms cons (randomR ival) g) -- | Plural variant of 'random', producing an infinite list of - -- random values instead of returning a new generator. + -- pseudo-random values instead of returning a new generator. {-# INLINE randoms #-} randoms :: RandomGen g => g -> [a] randoms g = build (\cons _nil -> buildRandoms cons random g) - -- | A variant of 'randomR' that uses the global random number generator - -- (see "System.Random#globalrng"). + -- | A variant of 'randomR' that uses the global pseudo-random number + -- generator (see "System.Random#globalrng"). randomRIO :: (a,a) -> IO a randomRIO range = getStdRandom (randomR range) - -- | A variant of 'random' that uses the global random number generator + -- | A variant of 'random' that uses the global pseudo-random number generator -- (see "System.Random#globalrng"). randomIO :: IO a randomIO = getStdRandom random --- | Produce an infinite list-equivalent of random values. +-- | Produce an infinite list-equivalent of pseudo-random values. {-# INLINE buildRandoms #-} buildRandoms :: RandomGen g => (a -> as -> as) -- ^ E.g. '(:)' but subject to fusion @@ -1459,13 +1462,13 @@ randomIvalInteger (l,h) rng -- will differ at most by a factor of (1 +- 1/q). Assuming the RandomGen -- is uniform, of course - -- On average, log q / log b more random values will be generated + -- On average, log q / log b more pseudo-random values will be generated -- than the minimum q = 1000 k = h - l + 1 magtgt = k * q - -- generate random values until we exceed the target magnitude + -- generate pseudo-random values until we exceed the target magnitude f mag v g | mag >= magtgt = (v, g) | otherwise = v' `seq`f (mag*b) v' g' where (x,g') = next g @@ -1529,7 +1532,7 @@ integerWordSize = go 0 | otherwise = go (acc + 1) (i `shiftR` WORD_SIZE_IN_BITS) {-# INLINE integerWordSize #-} --- | @uniformIntegerWords n@ is a uniformly random 'Integer' in the range +-- | @uniformIntegerWords n@ is a uniformly pseudo-random 'Integer' in the range -- @[0, WORD_SIZE_IN_BITS^n)@. uniformIntegerWords :: (MonadRandom g s m) => Int -> g s -> m Integer uniformIntegerWords n gen = go 0 n @@ -1655,22 +1658,22 @@ instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e, Uniform f) => U f <- uniform g return (a, b, c, d, e, f) --- The global random number generator +-- The global pseudo-random number generator {- $globalrng #globalrng# -There is a single, implicit, global random number generator of type +There is a single, implicit, global pseudo-random number generator of type 'StdGen', held in some global variable maintained by the 'IO' monad. It is initialised automatically in some system-dependent fashion, for example, by -using the time of day, or Linux's kernel random number generator. To get +using the time of day, or Linux's kernel pseudo-random number generator. To get deterministic behaviour, use 'setStdGen'. -} --- |Sets the global random number generator. +-- |Sets the global pseudo-random number generator. setStdGen :: StdGen -> IO () setStdGen sgen = writeIORef theStdGen sgen --- |Gets the global random number generator. +-- |Gets the global pseudo-random number generator. getStdGen :: IO StdGen getStdGen = readIORef theStdGen @@ -1678,14 +1681,14 @@ theStdGen :: IORef StdGen theStdGen = unsafePerformIO $ SM.initSMGen >>= newIORef . StdGen {-# NOINLINE theStdGen #-} --- |Applies 'split' to the current global random generator, +-- |Applies 'split' to the current global pseudo-random generator, -- updates it with one of the results, and returns the other. newStdGen :: IO StdGen newStdGen = atomicModifyIORef' theStdGen split {- |Uses the supplied function to get a value from the current global random generator, and updates the global generator with the new generator -returned by the function. For example, @rollDice@ gets a random integer +returned by the function. For example, @rollDice@ gets a pseudo-random integer between 1 and 6: > rollDice :: IO Int From 8ea4b902959374c23d7dbcee053057616b332521 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 10 Apr 2020 17:23:03 +0200 Subject: [PATCH 123/170] Use default implementation randomM = uniform --- System/Random.hs | 109 +++++++++++++++++------------------------------ 1 file changed, 38 insertions(+), 71 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 0ae8dd92a..eb609c3c7 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1030,7 +1030,7 @@ Minimal complete definition: 'randomR' and 'random'. -} {-# DEPRECATED randomRIO "In favor of `uniformR`" #-} -{-# DEPRECATED randomIO "In favor of `uniformR`" #-} +{-# DEPRECATED randomIO "In favor of `uniform`" #-} class Random a where -- | Takes a range /(lo,hi)/ and a pseudo-random number generator @@ -1059,8 +1059,8 @@ class Random a where --{-# INLINE randomM #-} randomM :: MonadRandom g s m => g s -> m a - -- default randomM :: (MonadRandom g m, Uniform a) => g -> m a - -- randomM = uniform + default randomM :: (MonadRandom g s m, Uniform a) => g s -> m a + randomM = uniform -- | Plural variant of 'randomR', producing an infinite list of -- pseudo-random values instead of returning a new generator. @@ -1075,12 +1075,12 @@ class Random a where randoms g = build (\cons _nil -> buildRandoms cons random g) -- | A variant of 'randomR' that uses the global pseudo-random number - -- generator (see "System.Random#globalrng"). + -- generator. randomRIO :: (a,a) -> IO a randomRIO range = getStdRandom (randomR range) - -- | A variant of 'random' that uses the global pseudo-random number generator - -- (see "System.Random#globalrng"). + -- | A variant of 'random' that uses the global pseudo-random number + -- generator. randomIO :: IO a randomIO = getStdRandom random @@ -1104,36 +1104,31 @@ instance Random Integer where instance UniformRange Integer where uniformR = uniformIntegerM -instance Random Int8 where - randomM = uniform +instance Random Int8 instance Uniform Int8 where uniform = fmap (fromIntegral :: Word8 -> Int8) . uniformWord8 instance UniformRange Int8 where uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int8 -> Word8) fromIntegral -instance Random Int16 where - randomM = uniform +instance Random Int16 instance Uniform Int16 where uniform = fmap (fromIntegral :: Word16 -> Int16) . uniformWord16 instance UniformRange Int16 where uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int16 -> Word16) fromIntegral -instance Random Int32 where - randomM = uniform +instance Random Int32 instance Uniform Int32 where uniform = fmap (fromIntegral :: Word32 -> Int32) . uniformWord32 instance UniformRange Int32 where uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int32 -> Word32) fromIntegral -instance Random Int64 where - randomM = uniform +instance Random Int64 instance Uniform Int64 where uniform = fmap (fromIntegral :: Word64 -> Int64) . uniformWord64 instance UniformRange Int64 where uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int64 -> Word64) fromIntegral -instance Random Int where - randomM = uniform +instance Random Int instance Uniform Int where #if WORD_SIZE_IN_BITS < 64 uniform = fmap (fromIntegral :: Word32 -> Int) . uniformWord32 @@ -1145,8 +1140,7 @@ instance UniformRange Int where uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int -> Word) fromIntegral {-# INLINE uniformR #-} -instance Random Word where - randomM = uniform +instance Random Word instance Uniform Word where #if WORD_SIZE_IN_BITS < 64 uniform = fmap (fromIntegral :: Word32 -> Word) . uniformWord32 @@ -1157,8 +1151,7 @@ instance UniformRange Word where {-# INLINE uniformR #-} uniformR = unsignedBitmaskWithRejectionRM -instance Random Word8 where - randomM = uniform +instance Random Word8 instance Uniform Word8 where {-# INLINE uniform #-} uniform = uniformWord8 @@ -1166,8 +1159,7 @@ instance UniformRange Word8 where {-# INLINE uniformR #-} uniformR = unsignedBitmaskWithRejectionRM -instance Random Word16 where - randomM = uniform +instance Random Word16 instance Uniform Word16 where {-# INLINE uniform #-} uniform = uniformWord16 @@ -1175,8 +1167,7 @@ instance UniformRange Word16 where {-# INLINE uniformR #-} uniformR = unsignedBitmaskWithRejectionRM -instance Random Word32 where - randomM = uniform +instance Random Word32 instance Uniform Word32 where {-# INLINE uniform #-} uniform = uniformWord32 @@ -1185,8 +1176,7 @@ instance UniformRange Word32 where uniformR (b, t) g | b > t = (+t) <$> unbiasedWordMult32 (b - t) g | otherwise = (+b) <$> unbiasedWordMult32 (t - b) g -instance Random Word64 where - randomM = uniform +instance Random Word64 instance Uniform Word64 where {-# INLINE uniform #-} uniform = uniformWord64 @@ -1194,141 +1184,121 @@ instance UniformRange Word64 where {-# INLINE uniformR #-} uniformR = unsignedBitmaskWithRejectionRM -instance Random CBool where - randomM = uniform +instance Random CBool instance Uniform CBool where uniform = fmap CBool . uniform instance UniformRange CBool where uniformR (CBool b, CBool t) = fmap CBool . uniformR (b, t) -instance Random CChar where - randomM = uniform +instance Random CChar instance Uniform CChar where uniform = fmap CChar . uniform instance UniformRange CChar where uniformR (CChar b, CChar t) = fmap CChar . uniformR (b, t) -instance Random CSChar where - randomM = uniform +instance Random CSChar instance Uniform CSChar where uniform = fmap CSChar . uniform instance UniformRange CSChar where uniformR (CSChar b, CSChar t) = fmap CSChar . uniformR (b, t) -instance Random CUChar where - randomM = uniform +instance Random CUChar instance Uniform CUChar where uniform = fmap CUChar . uniform instance UniformRange CUChar where uniformR (CUChar b, CUChar t) = fmap CUChar . uniformR (b, t) -instance Random CShort where - randomM = uniform +instance Random CShort instance Uniform CShort where uniform = fmap CShort . uniform instance UniformRange CShort where uniformR (CShort b, CShort t) = fmap CShort . uniformR (b, t) -instance Random CUShort where - randomM = uniform +instance Random CUShort instance Uniform CUShort where uniform = fmap CUShort . uniform instance UniformRange CUShort where uniformR (CUShort b, CUShort t) = fmap CUShort . uniformR (b, t) -instance Random CInt where - randomM = uniform +instance Random CInt instance Uniform CInt where uniform = fmap CInt . uniform instance UniformRange CInt where uniformR (CInt b, CInt t) = fmap CInt . uniformR (b, t) -instance Random CUInt where - randomM = uniform +instance Random CUInt instance Uniform CUInt where uniform = fmap CUInt . uniform instance UniformRange CUInt where uniformR (CUInt b, CUInt t) = fmap CUInt . uniformR (b, t) -instance Random CLong where - randomM = uniform +instance Random CLong instance Uniform CLong where uniform = fmap CLong . uniform instance UniformRange CLong where uniformR (CLong b, CLong t) = fmap CLong . uniformR (b, t) -instance Random CULong where - randomM = uniform +instance Random CULong instance Uniform CULong where uniform = fmap CULong . uniform instance UniformRange CULong where uniformR (CULong b, CULong t) = fmap CULong . uniformR (b, t) -instance Random CPtrdiff where - randomM = uniform +instance Random CPtrdiff instance Uniform CPtrdiff where uniform = fmap CPtrdiff . uniform instance UniformRange CPtrdiff where uniformR (CPtrdiff b, CPtrdiff t) = fmap CPtrdiff . uniformR (b, t) -instance Random CSize where - randomM = uniform +instance Random CSize instance Uniform CSize where uniform = fmap CSize . uniform instance UniformRange CSize where uniformR (CSize b, CSize t) = fmap CSize . uniformR (b, t) -instance Random CWchar where - randomM = uniform +instance Random CWchar instance Uniform CWchar where uniform = fmap CWchar . uniform instance UniformRange CWchar where uniformR (CWchar b, CWchar t) = fmap CWchar . uniformR (b, t) -instance Random CSigAtomic where - randomM = uniform +instance Random CSigAtomic instance Uniform CSigAtomic where uniform = fmap CSigAtomic . uniform instance UniformRange CSigAtomic where uniformR (CSigAtomic b, CSigAtomic t) = fmap CSigAtomic . uniformR (b, t) -instance Random CLLong where - randomM = uniform +instance Random CLLong instance Uniform CLLong where uniform = fmap CLLong . uniform instance UniformRange CLLong where uniformR (CLLong b, CLLong t) = fmap CLLong . uniformR (b, t) -instance Random CULLong where - randomM = uniform +instance Random CULLong instance Uniform CULLong where uniform = fmap CULLong . uniform instance UniformRange CULLong where uniformR (CULLong b, CULLong t) = fmap CULLong . uniformR (b, t) -instance Random CIntPtr where - randomM = uniform +instance Random CIntPtr instance Uniform CIntPtr where uniform = fmap CIntPtr . uniform instance UniformRange CIntPtr where uniformR (CIntPtr b, CIntPtr t) = fmap CIntPtr . uniformR (b, t) -instance Random CUIntPtr where - randomM = uniform +instance Random CUIntPtr instance Uniform CUIntPtr where uniform = fmap CUIntPtr . uniform instance UniformRange CUIntPtr where uniformR (CUIntPtr b, CUIntPtr t) = fmap CUIntPtr . uniformR (b, t) -instance Random CIntMax where - randomM = uniform +instance Random CIntMax instance Uniform CIntMax where uniform = fmap CIntMax . uniform instance UniformRange CIntMax where uniformR (CIntMax b, CIntMax t) = fmap CIntMax . uniformR (b, t) -instance Random CUIntMax where - randomM = uniform +instance Random CUIntMax instance Uniform CUIntMax where uniform = fmap CUIntMax . uniform instance UniformRange CUIntMax where @@ -1363,9 +1333,7 @@ charToWord32 :: Char -> Word32 charToWord32 (C# c#) = W32# (int2Word# (ord# c#)) {-# INLINE charToWord32 #-} -instance Random Char where - randomM = uniform - {-# INLINE randomM #-} +instance Random Char instance Uniform Char where uniform g = word32ToChar <$> unsignedBitmaskWithRejectionM uniform (charToWord32 maxBound) g {-# INLINE uniform #-} @@ -1374,8 +1342,7 @@ instance UniformRange Char where word32ToChar <$> unsignedBitmaskWithRejectionRM (charToWord32 l, charToWord32 h) g {-# INLINE uniformR #-} -instance Random Bool where - randomM = uniform +instance Random Bool instance Uniform Bool where uniform = fmap wordToBool . uniformWord8 where wordToBool w = (w .&. 1) /= 0 From 97c26af26cb0c96b4c7e1fe5c1305cc95aa00d5f Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Tue, 14 Apr 2020 17:02:59 +0200 Subject: [PATCH 124/170] Undo randomM default implementation --- System/Random.hs | 99 +++++++++++++++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 34 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index eb609c3c7..b36518038 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1059,8 +1059,6 @@ class Random a where --{-# INLINE randomM #-} randomM :: MonadRandom g s m => g s -> m a - default randomM :: (MonadRandom g s m, Uniform a) => g s -> m a - randomM = uniform -- | Plural variant of 'randomR', producing an infinite list of -- pseudo-random values instead of returning a new generator. @@ -1104,31 +1102,36 @@ instance Random Integer where instance UniformRange Integer where uniformR = uniformIntegerM -instance Random Int8 +instance Random Int8 where + randomM = uniform instance Uniform Int8 where uniform = fmap (fromIntegral :: Word8 -> Int8) . uniformWord8 instance UniformRange Int8 where uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int8 -> Word8) fromIntegral -instance Random Int16 +instance Random Int16 where + randomM = uniform instance Uniform Int16 where uniform = fmap (fromIntegral :: Word16 -> Int16) . uniformWord16 instance UniformRange Int16 where uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int16 -> Word16) fromIntegral -instance Random Int32 +instance Random Int32 where + randomM = uniform instance Uniform Int32 where uniform = fmap (fromIntegral :: Word32 -> Int32) . uniformWord32 instance UniformRange Int32 where uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int32 -> Word32) fromIntegral -instance Random Int64 +instance Random Int64 where + randomM = uniform instance Uniform Int64 where uniform = fmap (fromIntegral :: Word64 -> Int64) . uniformWord64 instance UniformRange Int64 where uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int64 -> Word64) fromIntegral -instance Random Int +instance Random Int where + randomM = uniform instance Uniform Int where #if WORD_SIZE_IN_BITS < 64 uniform = fmap (fromIntegral :: Word32 -> Int) . uniformWord32 @@ -1140,7 +1143,8 @@ instance UniformRange Int where uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int -> Word) fromIntegral {-# INLINE uniformR #-} -instance Random Word +instance Random Word where + randomM = uniform instance Uniform Word where #if WORD_SIZE_IN_BITS < 64 uniform = fmap (fromIntegral :: Word32 -> Word) . uniformWord32 @@ -1151,7 +1155,8 @@ instance UniformRange Word where {-# INLINE uniformR #-} uniformR = unsignedBitmaskWithRejectionRM -instance Random Word8 +instance Random Word8 where + randomM = uniform instance Uniform Word8 where {-# INLINE uniform #-} uniform = uniformWord8 @@ -1159,7 +1164,8 @@ instance UniformRange Word8 where {-# INLINE uniformR #-} uniformR = unsignedBitmaskWithRejectionRM -instance Random Word16 +instance Random Word16 where + randomM = uniform instance Uniform Word16 where {-# INLINE uniform #-} uniform = uniformWord16 @@ -1167,7 +1173,8 @@ instance UniformRange Word16 where {-# INLINE uniformR #-} uniformR = unsignedBitmaskWithRejectionRM -instance Random Word32 +instance Random Word32 where + randomM = uniform instance Uniform Word32 where {-# INLINE uniform #-} uniform = uniformWord32 @@ -1176,7 +1183,8 @@ instance UniformRange Word32 where uniformR (b, t) g | b > t = (+t) <$> unbiasedWordMult32 (b - t) g | otherwise = (+b) <$> unbiasedWordMult32 (t - b) g -instance Random Word64 +instance Random Word64 where + randomM = uniform instance Uniform Word64 where {-# INLINE uniform #-} uniform = uniformWord64 @@ -1184,121 +1192,141 @@ instance UniformRange Word64 where {-# INLINE uniformR #-} uniformR = unsignedBitmaskWithRejectionRM -instance Random CBool +instance Random CBool where + randomM = uniform instance Uniform CBool where uniform = fmap CBool . uniform instance UniformRange CBool where uniformR (CBool b, CBool t) = fmap CBool . uniformR (b, t) -instance Random CChar +instance Random CChar where + randomM = uniform instance Uniform CChar where uniform = fmap CChar . uniform instance UniformRange CChar where uniformR (CChar b, CChar t) = fmap CChar . uniformR (b, t) -instance Random CSChar +instance Random CSChar where + randomM = uniform instance Uniform CSChar where uniform = fmap CSChar . uniform instance UniformRange CSChar where uniformR (CSChar b, CSChar t) = fmap CSChar . uniformR (b, t) -instance Random CUChar +instance Random CUChar where + randomM = uniform instance Uniform CUChar where uniform = fmap CUChar . uniform instance UniformRange CUChar where uniformR (CUChar b, CUChar t) = fmap CUChar . uniformR (b, t) -instance Random CShort +instance Random CShort where + randomM = uniform instance Uniform CShort where uniform = fmap CShort . uniform instance UniformRange CShort where uniformR (CShort b, CShort t) = fmap CShort . uniformR (b, t) -instance Random CUShort +instance Random CUShort where + randomM = uniform instance Uniform CUShort where uniform = fmap CUShort . uniform instance UniformRange CUShort where uniformR (CUShort b, CUShort t) = fmap CUShort . uniformR (b, t) -instance Random CInt +instance Random CInt where + randomM = uniform instance Uniform CInt where uniform = fmap CInt . uniform instance UniformRange CInt where uniformR (CInt b, CInt t) = fmap CInt . uniformR (b, t) -instance Random CUInt +instance Random CUInt where + randomM = uniform instance Uniform CUInt where uniform = fmap CUInt . uniform instance UniformRange CUInt where uniformR (CUInt b, CUInt t) = fmap CUInt . uniformR (b, t) -instance Random CLong +instance Random CLong where + randomM = uniform instance Uniform CLong where uniform = fmap CLong . uniform instance UniformRange CLong where uniformR (CLong b, CLong t) = fmap CLong . uniformR (b, t) -instance Random CULong +instance Random CULong where + randomM = uniform instance Uniform CULong where uniform = fmap CULong . uniform instance UniformRange CULong where uniformR (CULong b, CULong t) = fmap CULong . uniformR (b, t) -instance Random CPtrdiff +instance Random CPtrdiff where + randomM = uniform instance Uniform CPtrdiff where uniform = fmap CPtrdiff . uniform instance UniformRange CPtrdiff where uniformR (CPtrdiff b, CPtrdiff t) = fmap CPtrdiff . uniformR (b, t) -instance Random CSize +instance Random CSize where + randomM = uniform instance Uniform CSize where uniform = fmap CSize . uniform instance UniformRange CSize where uniformR (CSize b, CSize t) = fmap CSize . uniformR (b, t) -instance Random CWchar +instance Random CWchar where + randomM = uniform instance Uniform CWchar where uniform = fmap CWchar . uniform instance UniformRange CWchar where uniformR (CWchar b, CWchar t) = fmap CWchar . uniformR (b, t) -instance Random CSigAtomic +instance Random CSigAtomic where + randomM = uniform instance Uniform CSigAtomic where uniform = fmap CSigAtomic . uniform instance UniformRange CSigAtomic where uniformR (CSigAtomic b, CSigAtomic t) = fmap CSigAtomic . uniformR (b, t) -instance Random CLLong +instance Random CLLong where + randomM = uniform instance Uniform CLLong where uniform = fmap CLLong . uniform instance UniformRange CLLong where uniformR (CLLong b, CLLong t) = fmap CLLong . uniformR (b, t) -instance Random CULLong +instance Random CULLong where + randomM = uniform instance Uniform CULLong where uniform = fmap CULLong . uniform instance UniformRange CULLong where uniformR (CULLong b, CULLong t) = fmap CULLong . uniformR (b, t) -instance Random CIntPtr +instance Random CIntPtr where + randomM = uniform instance Uniform CIntPtr where uniform = fmap CIntPtr . uniform instance UniformRange CIntPtr where uniformR (CIntPtr b, CIntPtr t) = fmap CIntPtr . uniformR (b, t) -instance Random CUIntPtr +instance Random CUIntPtr where + randomM = uniform instance Uniform CUIntPtr where uniform = fmap CUIntPtr . uniform instance UniformRange CUIntPtr where uniformR (CUIntPtr b, CUIntPtr t) = fmap CUIntPtr . uniformR (b, t) -instance Random CIntMax +instance Random CIntMax where + randomM = uniform instance Uniform CIntMax where uniform = fmap CIntMax . uniform instance UniformRange CIntMax where uniformR (CIntMax b, CIntMax t) = fmap CIntMax . uniformR (b, t) -instance Random CUIntMax +instance Random CUIntMax where + randomM = uniform instance Uniform CUIntMax where uniform = fmap CUIntMax . uniform instance UniformRange CUIntMax where @@ -1333,7 +1361,9 @@ charToWord32 :: Char -> Word32 charToWord32 (C# c#) = W32# (int2Word# (ord# c#)) {-# INLINE charToWord32 #-} -instance Random Char +instance Random Char where + randomM = uniform + {-# INLINE randomM #-} instance Uniform Char where uniform g = word32ToChar <$> unsignedBitmaskWithRejectionM uniform (charToWord32 maxBound) g {-# INLINE uniform #-} @@ -1342,7 +1372,8 @@ instance UniformRange Char where word32ToChar <$> unsignedBitmaskWithRejectionRM (charToWord32 l, charToWord32 h) g {-# INLINE uniformR #-} -instance Random Bool +instance Random Bool where + randomM = uniform instance Uniform Bool where uniform = fmap wordToBool . uniformWord8 where wordToBool w = (w .&. 1) /= 0 From ffc486563f45ad578e2f323d106819d01960587b Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Tue, 14 Apr 2020 17:13:36 +0200 Subject: [PATCH 125/170] IOGen and STGen are safe in presence of exceptions --- System/Random.hs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index b36518038..82434e2c1 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -124,12 +124,10 @@ -- it performs all actions atomically. -- -- * 'IOGen' is a wrapper around an 'IORef' that holds a pure generator. --- 'IOGen' is /not/ safe to use in the presence of exceptions and --- concurrency. +-- 'IOGen' is safe in the presence of exceptions, but not concurrency. -- -- * 'STGen' is a wrapper around an 'STRef' that holds a pure generator. --- 'STGen' is /not/ safe to use in the presence of exceptions and --- concurrency. +-- 'STGen' is safe in the presence of exceptions, but not concurrency. -- -- When to use which? -- @@ -137,15 +135,13 @@ -- in a pure function. -- -- * Use 'AtomicGen' if the pseudo-random number generator must be shared --- between threads or if exception safety is a requirement. +-- between threads. -- --- * Use 'IOGen' if operating in a 'MonadIO' context, exception safety is not --- a concern and the pseudo-random number generator is not shared between --- threads. +-- * Use 'IOGen' if operating in a 'MonadIO' context and the pseudo-random +-- number generator is not shared between threads. -- --- * Use 'STGen' if operating in an 'ST' context, exception safety is not --- a concern and the pseudo-random number generator is not shared between --- threads. +-- * Use 'STGen' if operating in an 'ST' context and the pseudo-random number +-- generator is not shared between threads. -- -- = How to generate pseudo-random values in monadic code -- From 8bf21d091850983a673414d8cb0500c229ba32f4 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Tue, 14 Apr 2020 17:26:14 +0200 Subject: [PATCH 126/170] Use applicative syntax for Uniform tuple instances --- System/Random.hs | 38 ++++++++------------------------------ 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 82434e2c1..6ff59405b 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1613,44 +1613,22 @@ unsignedBitmaskWithRejectionM genUniform range gen = go ------------------------------------------------------------------------------- instance (Uniform a, Uniform b) => Uniform (a, b) where - uniform g = do - a <- uniform g - b <- uniform g - return (a, b) + uniform g = (,) <$> uniform g <*> uniform g instance (Uniform a, Uniform b, Uniform c) => Uniform (a, b, c) where - uniform g = do - a <- uniform g - b <- uniform g - c <- uniform g - return (a, b, c) + uniform g = (,,) <$> uniform g <*> uniform g <*> uniform g instance (Uniform a, Uniform b, Uniform c, Uniform d) => Uniform (a, b, c, d) where - uniform g = do - a <- uniform g - b <- uniform g - c <- uniform g - d <- uniform g - return (a, b, c, d) + uniform g = (,,,) <$> uniform g <*> uniform g <*> uniform g <*> uniform g instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e) => Uniform (a, b, c, d, e) where - uniform g = do - a <- uniform g - b <- uniform g - c <- uniform g - d <- uniform g - e <- uniform g - return (a, b, c, d, e) + uniform g = (,,,,) <$> uniform g <*> uniform g <*> uniform g <*> uniform g <*> uniform g instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e, Uniform f) => Uniform (a, b, c, d, e, f) where - uniform g = do - a <- uniform g - b <- uniform g - c <- uniform g - d <- uniform g - e <- uniform g - f <- uniform g - return (a, b, c, d, e, f) + uniform g = (,,,,,) <$> uniform g <*> uniform g <*> uniform g <*> uniform g <*> uniform g <*> uniform g + +instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e, Uniform f, Uniform g) => Uniform (a, b, c, d, e, f, g) where + uniform g = (,,,,,,) <$> uniform g <*> uniform g <*> uniform g <*> uniform g <*> uniform g <*> uniform g <*> uniform g -- The global pseudo-random number generator From baa5673d0cddef0971dbe3023824565dfd7df9fc Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Wed, 15 Apr 2020 10:24:09 +0200 Subject: [PATCH 127/170] Remove "When to use which?" section --- System/Random.hs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 6ff59405b..e39a89c2b 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -129,20 +129,6 @@ -- * 'STGen' is a wrapper around an 'STRef' that holds a pure generator. -- 'STGen' is safe in the presence of exceptions, but not concurrency. -- --- When to use which? --- --- * Use 'PureGen' if your computation does not have side effects and results --- in a pure function. --- --- * Use 'AtomicGen' if the pseudo-random number generator must be shared --- between threads. --- --- * Use 'IOGen' if operating in a 'MonadIO' context and the pseudo-random --- number generator is not shared between threads. --- --- * Use 'STGen' if operating in an 'ST' context and the pseudo-random number --- generator is not shared between threads. --- -- = How to generate pseudo-random values in monadic code -- -- In monadic code, use the relevant 'Uniform' and 'UniformRange' instances to From 50ba9f1290468ee0d95ad8bcdd37fad9316f00ca Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Wed, 15 Apr 2020 10:25:58 +0200 Subject: [PATCH 128/170] Revert "Make StdGen opaque" This reverts commit a645555522f16ac2528ce5150bddd7dc3e098687. --- System/Random.hs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index e39a89c2b..4765f5c09 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -627,7 +627,7 @@ instance RandomGen r => RandomGenM STGen r s (ST s) where -- -- >>> import Data.Int (Int8) -- >>> runGenM (IOGen (mkStdGen 217)) (`uniformListM` 5) :: IO ([Int8], Frozen (IOGen StdGen)) --- ([-74,37,-50,-2,3],IOGen {unIOGen = StdGen {unStdGen = SMGen 4273268533320920145 15251669095119325999}}) +-- ([-74,37,-50,-2,3],IOGen {unIOGen = SMGen 4273268533320920145 15251669095119325999}) -- -- @since 1.2 runGenM :: MonadRandom g s m => Frozen g -> (g s -> m a) -> m (a, Frozen g) @@ -941,21 +941,18 @@ runSTGen g action = unSTGen <$> runST (runGenM (STGen g) action) runSTGen_ :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> a runSTGen_ g action = fst $ runSTGen g action --- | The standard pseudo-random number generator. Uses the SplitMix --- implementation provided by the --- [splitmix](https://hackage.haskell.org/package/splitmix) package. -newtype StdGen = StdGen { unStdGen :: SM.SMGen } - deriving Show +-- | The standard pseudo-random number generator. +type StdGen = SM.SMGen instance RandomGen StdGen where - next = second StdGen . SM.nextInt . unStdGen - genWord32 = second StdGen . SM.nextWord32 . unStdGen - genWord64 = second StdGen . SM.nextWord64 . unStdGen - split g = let (g1, g2) = SM.splitSMGen $ unStdGen g in (StdGen g1, StdGen g2) + next = SM.nextInt + genWord32 = SM.nextWord32 + genWord64 = SM.nextWord64 + split = SM.splitSMGen -- | Constructs a 'StdGen' deterministically. mkStdGen :: Int -> StdGen -mkStdGen = StdGen . SM.mkSMGen . fromIntegral +mkStdGen s = SM.mkSMGen $ fromIntegral s -- $uniform @@ -1636,7 +1633,7 @@ getStdGen :: IO StdGen getStdGen = readIORef theStdGen theStdGen :: IORef StdGen -theStdGen = unsafePerformIO $ SM.initSMGen >>= newIORef . StdGen +theStdGen = unsafePerformIO $ SM.initSMGen >>= newIORef {-# NOINLINE theStdGen #-} -- |Applies 'split' to the current global pseudo-random generator, From 899dbfc26f900046d266cfd900fd97114961ff63 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Wed, 15 Apr 2020 10:37:17 +0200 Subject: [PATCH 129/170] Re-introduce radian angles example --- System/Random.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/System/Random.hs b/System/Random.hs index 4765f5c09..975b96de7 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -970,6 +970,10 @@ mkStdGen s = SM.mkSMGen $ fromIntegral s -- 'Uniform'. -- * 'Integer', 'Float' and 'Double' each have an instance for 'UniformRange' -- but no 'Uniform' instance. +-- * A hypothetical type @Radian@ representing angles by taking values in the +-- range @[0, 2π)@ has a trivial 'Uniform' instance, but no 'UniformRange' +-- instance: the problem is that two given @Radian@ values always span /two/ +-- ranges, one clockwise and one anti-clockwise. -- * It is trivial to construct a @Uniform (a, b)@ instance given -- @Uniform a@ and @Uniform b@ (and this library provides this tuple -- instance). From 71c8e6fc66b7095fd62b55c401d4fd7b14bdb973 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Wed, 15 Apr 2020 15:28:28 +0200 Subject: [PATCH 130/170] Structure Haddocks --- System/Random.hs | 702 +++++++++++++++++++++++++---------------------- 1 file changed, 370 insertions(+), 332 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 975b96de7..e0501f1a1 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -17,7 +17,6 @@ #include "MachDeps.h" ------------------------------------------------------------------------------ -- | -- Module : System.Random -- Copyright : (c) The University of Glasgow 2001 @@ -26,282 +25,20 @@ -- Stability : stable -- -- This library deals with the common task of pseudo-random number generation. --- --- = Overview --- This library provides type classes and instances for the following concepts: --- --- [Pure pseudo-random number generators] 'RandomGen' is an interface to pure --- pseudo-random number generators. --- --- 'StdGen', the standard pseudo-random number generator provided in this --- library, is an instance of 'RandomGen'. It uses the SplitMix --- implementation provided by the --- package. --- Programmers may, of course, supply their own instances of 'RandomGen'. --- --- [Monadic pseudo-random number generators] 'MonadRandom' is an interface to --- monadic pseudo-random number generators. --- --- [Monadic adapters] 'PureGen', 'AtomicGen', 'IOGen' and 'STGen' turn a --- 'RandomGen' instance into a 'MonadRandom' instance. --- --- [Drawing from a range] 'UniformRange' is used to generate a value of a --- datatype uniformly within an inclusive range. --- --- This library provides instances of 'UniformRange' for many common --- numeric datatypes. --- --- [Drawing from the entire domain of a type] 'Uniform' is used to generate a --- value of a datatype uniformly over all possible values of that datatype. --- --- This library provides instances of 'Uniform' for many common bounded --- numeric datatypes. --- --- = Backwards compatibility and deprecations --- --- Version 1.2 mostly maintains backwards compatibility with version 1.1. This --- has a few consequences users should be aware of: --- --- * The type class 'Random' is deprecated and only provided for backwards --- compatibility. New code should use 'Uniform' and 'UniformRange' instead. --- --- * The methods 'next' and 'genRange' in 'RandomGen' are deprecated and only --- provided for backwards compatibility. New instances of 'RandomGen' should --- implement word-based methods instead. See below for more information --- about how to write a 'RandomGen' instance. --- --- * This library provides instances for 'Random' for some unbounded datatypes --- for backwards compatibility. For an unbounded datatype, there is no way --- to generate a value with uniform probability out of its entire domain, so --- the 'random' implementation for unbounded datatypes actually generates a --- value based on some fixed range. --- --- For 'Integer', 'random' generates a value in the 'Int' range. For 'Float' --- and 'Double', 'random' generates a floating point value in the range @[0, --- 1)@. --- --- This library does not provide 'Uniform' instances for any unbounded --- datatypes. --- --- = Reproducibility --- --- If you have two builds of a particular piece of code against this library, --- any deterministic function call should give the same result in the two --- builds if the builds are --- --- * compiled against the same major version of this library --- * on the same architecture (32-bit or 64-bit) --- --- = Pure and monadic pseudo-random number generators --- --- Pseudo-random number generators come in two flavours: /pure/ and /monadic/. --- --- [Pure pseudo-random number generators] These generators produce a new --- pseudo-random value together with a new instance of the pseudo-random --- number generator. 'RandomGen' defines the interface for pure --- pseudo-random number generators. --- --- Pure pseudo-random number generators should implement 'split' if they --- are /splittable/, that is, if there is an efficient method to turn one --- instance of the generator into two such that the pseudo-random numbers --- produced by the two resulting generators are not correlated. See [1] for --- some background on splittable pseudo-random generators. --- --- [Monadic pseudo-random number generators] These generators mutate their own --- state as they produce pseudo-random values. They generally live in 'ST' --- or 'IO' or some transformer that implements @PrimMonad@. 'MonadRandom' --- defines the interface for monadic pseudo-random number generators. --- --- Pure pseudo-random number generators can be used in monadic code via the --- adapters 'PureGen', 'AtomicGen', 'IOGen' and 'STGen'. --- --- * 'PureGen' can be used in any state monad. With strict 'StateT' there is --- no performance overhead compared to using the 'RandomGen' instance --- directly. 'PureGen' is /not/ safe to use in the presence of exceptions --- and concurrency. --- --- * 'AtomicGen' is safe in the presence of exceptions and concurrency since --- it performs all actions atomically. --- --- * 'IOGen' is a wrapper around an 'IORef' that holds a pure generator. --- 'IOGen' is safe in the presence of exceptions, but not concurrency. --- --- * 'STGen' is a wrapper around an 'STRef' that holds a pure generator. --- 'STGen' is safe in the presence of exceptions, but not concurrency. --- --- = How to generate pseudo-random values in monadic code --- --- In monadic code, use the relevant 'Uniform' and 'UniformRange' instances to --- generate pseudo-random values via 'uniform' and 'uniformR', respectively. --- --- As an example, @rolls@ generates @n@ pseudo-random values of @Word8@ in the --- range @[1, 6]@. --- --- >>> :{ --- let rolls :: MonadRandom g s m => Int -> g s -> m [Word8] --- rolls n = replicateM n . uniformR (1, 6) --- :} --- --- Given a /monadic/ pseudo-random number generator, you can run this --- probabilistic computation as follows: --- --- >>> monadicGen <- MWC.create --- >>> rolls 10 monadicGen :: IO [Word8] --- [2,3,6,6,4,4,3,1,5,4] --- --- Given a /pure/ pseudo-random number generator, you can run it in an 'IO' or --- 'ST' context by first applying a monadic adapter like 'AtomicGen', 'IOGen' --- or 'STGen' and then running it with 'runGenM'. --- --- >>> let pureGen = mkStdGen 42 --- >>> runGenM_ (IOGen pureGen) (rolls 10) :: IO [Word8] --- [1,1,3,2,4,5,3,4,6,2] --- --- = How to generate pseudo-random values in pure code --- --- In pure code, use 'runGenState' and its variants to extract the pure --- pseudo-random value from a monadic computation based on a pure pseudo-random --- number generator. --- --- >>> let pureGen = mkStdGen 42 --- >>> runGenState_ pureGen (rolls 10) :: [Word8] --- [1,1,3,2,4,5,3,4,6,2] --- --- = How to implement 'RandomGen' --- --- Consider these points when writing a 'RandomGen' instance for a given pure --- pseudo-random number generator: --- --- * If the pseudo-random number generator has a power-of-2 modulus, that is, --- it natively outputs @2^n@ bits of randomness for some @n@, implement --- 'genWord8', 'genWord16', 'genWord32' and 'genWord64'. See below for more --- details. --- --- * If the pseudo-random number generator does not have a power-of-2 --- modulus, implement 'next' and 'genRange'. See below for more details. --- --- * If the pseudo-random number generator is splittable, implement 'split'. --- If there is no suitable implementation, 'split' should fail with a --- helpful error message. --- --- == How to implement 'RandomGen' for a pseudo-random number generator with power-of-2 modulus --- --- Suppose you want to implement a [permuted congruential --- generator](https://en.wikipedia.org/wiki/Permuted_congruential_generator). --- --- >>> data PCGen = PCGen !Word64 !Word64 --- --- It produces a full 'Word32' of randomness per iteration. --- --- >>> :{ --- let stepGen :: PCGen -> (Word32, PCGen) --- stepGen (PCGen state inc) = let --- newState = state * 6364136223846793005 + (inc .|. 1) --- xorShifted = fromIntegral (((state `shiftR` 18) `xor` state) `shiftR` 27) :: Word32 --- rot = fromIntegral (state `shiftR` 59) :: Word32 --- out = (xorShifted `shiftR` (fromIntegral rot)) .|. (xorShifted `shiftL` fromIntegral ((-rot) .&. 31)) --- in (out, PCGen newState inc) --- :} --- --- >>> fst $ stepGen $ snd $ stepGen (PCGen 17 29) --- 3288430965 --- --- You can make it an instance of 'RandomGen' as follows: --- --- >>> :{ --- instance RandomGen PCGen where --- genWord32 = stepGen --- genWord64 g = (buildWord64 x y, g'') --- where --- (x, g') = stepGen g --- (y, g'') = stepGen g' --- :} --- --- This definition satisfies the compiler. However, the default implementations --- of 'genWord8' and 'genWord16' are geared towards backwards compatibility --- with 'RandomGen' instances based on 'next' and 'genRange'. This means that --- they are not optimal for pseudo-random number generators with a power-of-2 --- modulo. --- --- So let's implement a faster 'RandomGen' instance for our pseudo-random --- number generator as follows: --- --- >>> newtype PCGen' = PCGen' { unPCGen :: PCGen } --- >>> let stepGen' = second PCGen' . stepGen . unPCGen --- >>> :{ --- instance RandomGen PCGen' where --- genWord8 = first fromIntegral . stepGen' --- genWord16 = first fromIntegral . stepGen' --- genWord32 = stepGen' --- genWord64 g = (buildWord64 x y, g'') --- where --- (x, g') = stepGen' g --- (y, g'') = stepGen' g' --- :} --- --- == How to implement 'RandomGen' for a pseudo-random number generator without a power-of-2 modulus --- --- __We do not recommend you implement any new pseudo-random number generators without a power-of-2 modulus.__ --- --- Pseudo-random number generators without a power-of-2 modulus perform --- /significantly worse/ than pseudo-random number generators with a power-of-2 --- modulus with this library. This is because most functionality in this --- library is based on generating and transforming uniformly pseudo-random --- machine words, and generating uniformly pseudo-random machine words using a --- pseudo-random number generator without a power-of-2 modulus is expensive. --- --- The pseudo-random number generator from --- natively --- generates an integer value in the range @[1, 2147483562]@. This is the --- generator used by this library before it was replaced by SplitMix in version --- 1.2. --- --- >>> data LegacyGen = LegacyGen !Int32 !Int32 --- >>> :{ --- let legacyNext :: LegacyGen -> (Int, LegacyGen) --- legacyNext (LegacyGen s1 s2) = (fromIntegral z', LegacyGen s1'' s2'') where --- z' = if z < 1 then z + 2147483562 else z --- z = s1'' - s2'' --- k = s1 `quot` 53668 --- s1' = 40014 * (s1 - k * 53668) - k * 12211 --- s1'' = if s1' < 0 then s1' + 2147483563 else s1' --- k' = s2 `quot` 52774 --- s2' = 40692 * (s2 - k' * 52774) - k' * 3791 --- s2'' = if s2' < 0 then s2' + 2147483399 else s2' --- :} --- --- You can make it an instance of 'RandomGen' as follows: --- --- >>> :{ --- instance RandomGen LegacyGen where --- next = legacyNext --- genRange _ = (1, 2147483562) --- :} --- --- = How to implement 'MonadRandom' --- --- Typically, a monadic pseudo-random number generator has facilities to save --- and restore its internal state in addition to generating pseudo-random --- pseudo-random numbers. --- --- Here is an example instance for the monadic pseudo-random number generator --- from the @mwc-random@ package: --- --- > instance (s ~ PrimState m, PrimMonad m) => MonadRandom MWC.Gen s m where --- > newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } --- > thawGen = fmap MWC.restore unFrozen --- > freezeGen = fmap Frozen . MWC.save --- > uniformWord8 = MWC.uniform --- > uniformWord16 = MWC.uniform --- > uniformWord32 = MWC.uniform --- > uniformWord64 = MWC.uniform --- > uniformShortByteString n g = unsafeSTToPrim (genShortByteStringST n (MWC.uniform g)) ------------------------------------------------------------------------------ - module System.Random ( + -- * Introduction + -- $introduction - -- * Pseudo-random number generator interfaces + -- * Usage + -- ** How to generate pseudo-random values in monadic code + -- $usagemonadic + + -- ** How to generate pseudo-random values in pure code + -- $usagepure + + -- * Pure and monadic pseudo-random number generator interfaces + -- $interfaces RandomGen(..) , MonadRandom(..) , Frozen(..) @@ -309,11 +46,21 @@ module System.Random , runGenM_ , RandomGenM(..) , splitRandomGenM + -- ** Standard pseudo-random number generator , StdGen , mkStdGen + -- ** Global standard pseudo-random number generator + -- $globalstdgen + , getStdRandom + , getStdGen + , setStdGen + , newStdGen + -- * Monadic adapters for pure pseudo-random number generators + -- $monadicadapters + -- ** Pure adapter , PureGen , splitGen @@ -323,26 +70,18 @@ module System.Random , runGenStateT , runGenStateT_ , runPureGenST - -- ** Mutable adapters - -- *** AtomicGen + -- ** Mutable adapter with atomic operations , AtomicGen , applyAtomicGen - -- *** IOGen + -- ** Mutable adapter in 'IO' , IOGen , applyIOGen - -- *** STGen + -- ** Mutable adapter in 'ST' , STGen , applySTGen , runSTGen , runSTGen_ - -- ** The global pseudo-random number generator - -- $globalrng - , getStdRandom - , getStdGen - , setStdGen - , newStdGen - -- * Pseudo-random values of various types -- $uniform , Uniform(..) @@ -356,6 +95,20 @@ module System.Random , uniformByteString , genByteString + -- * Compatibility and reproducibility + -- ** Backwards compatibility and deprecations + -- $deprecations + + -- ** Reproducibility + -- $reproducibility + + -- * Notes for pseudo-random number generator implementors + -- ** How to implement 'RandomGen' + -- $implementrandomgen + + -- ** How to implement 'MonadRandom' + -- $implementmonadrandom + -- * References -- $references ) where @@ -385,40 +138,98 @@ import qualified System.Random.SplitMix as SM import GHC.Word import GHC.IO (IO(..)) --- $setup --- >>> import Control.Arrow (first, second) --- >>> import Control.Monad (replicateM) --- >>> import Control.Monad.Primitive --- >>> import Data.Bits --- >>> import Data.Int (Int32) --- >>> import Data.Word (Word8, Word16, Word32, Word64) --- >>> import System.IO (IOMode(WriteMode), withBinaryFile) --- >>> import qualified System.Random.MWC as MWC +-- $introduction +-- +-- This library provides type classes and instances for the following concepts: +-- +-- [Pure pseudo-random number generators] 'RandomGen' is an interface to pure +-- pseudo-random number generators. +-- +-- 'StdGen', the standard pseudo-random number generator provided in this +-- library, is an instance of 'RandomGen'. It uses the SplitMix +-- implementation provided by the +-- package. +-- Programmers may, of course, supply their own instances of 'RandomGen'. +-- +-- [Monadic pseudo-random number generators] 'MonadRandom' is an interface to +-- monadic pseudo-random number generators. +-- +-- [Monadic adapters] 'PureGen', 'AtomicGen', 'IOGen' and 'STGen' turn a +-- 'RandomGen' instance into a 'MonadRandom' instance. +-- +-- [Drawing from a range] 'UniformRange' is used to generate a value of a +-- datatype uniformly within an inclusive range. +-- +-- This library provides instances of 'UniformRange' for many common +-- numeric datatypes. +-- +-- [Drawing from the entire domain of a type] 'Uniform' is used to generate a +-- value of a datatype uniformly over all possible values of that datatype. +-- +-- This library provides instances of 'Uniform' for many common bounded +-- numeric datatypes. +-- +-- $usagemonadic +-- +-- In monadic code, use the relevant 'Uniform' and 'UniformRange' instances to +-- generate pseudo-random values via 'uniform' and 'uniformR', respectively. +-- +-- As an example, @rolls@ generates @n@ pseudo-random values of @Word8@ in the +-- range @[1, 6]@. +-- +-- >>> :{ +-- let rolls :: MonadRandom g s m => Int -> g s -> m [Word8] +-- rolls n = replicateM n . uniformR (1, 6) +-- :} +-- +-- Given a /monadic/ pseudo-random number generator, you can run this +-- probabilistic computation as follows: +-- +-- >>> monadicGen <- MWC.create +-- >>> rolls 10 monadicGen :: IO [Word8] +-- [2,3,6,6,4,4,3,1,5,4] +-- +-- Given a /pure/ pseudo-random number generator, you can run it in an 'IO' or +-- 'ST' context by first applying a monadic adapter like 'AtomicGen', 'IOGen' +-- or 'STGen' and then running it with 'runGenM'. +-- +-- >>> let pureGen = mkStdGen 42 +-- >>> runGenM_ (IOGen pureGen) (rolls 10) :: IO [Word8] +-- [1,1,3,2,4,5,3,4,6,2] +-- +-- $usagepure +-- +-- In pure code, use 'runGenState' and its variants to extract the pure +-- pseudo-random value from a monadic computation based on a pure pseudo-random +-- number generator. +-- +-- >>> let pureGen = mkStdGen 42 +-- >>> runGenState_ pureGen (rolls 10) :: [Word8] +-- [1,1,3,2,4,5,3,4,6,2] + +------------------------------------------------------------------------------- +-- Pseudo-random number generator interfaces +------------------------------------------------------------------------------- + +-- $interfaces -- --- >>> :set -XFlexibleContexts --- >>> :set -XFlexibleInstances --- >>> :set -XMultiParamTypeClasses --- >>> :set -XTypeFamilies --- >>> :set -XUndecidableInstances +-- Pseudo-random number generators come in two flavours: /pure/ and /monadic/. -- --- >>> :set -fno-warn-missing-methods +-- ['RandomGen': pure pseudo-random number generators] These generators produce +-- a new pseudo-random value together with a new instance of the +-- pseudo-random number generator. -- --- >>> :{ --- let buildWord64 :: Word32 -> Word32 -> Word64 --- buildWord64 x y = (fromIntegral x `shiftL` 32) .|. fromIntegral y --- :} +-- Pure pseudo-random number generators should implement 'split' if they +-- are /splittable/, that is, if there is an efficient method to turn one +-- instance of the generator into two such that the pseudo-random numbers +-- produced by the two resulting generators are not correlated. See [1] for +-- some background on splittable pseudo-random generators. +-- +-- ['MonadRandom': monadic pseudo-random number generators] These generators +-- mutate their own state as they produce pseudo-random values. They +-- generally live in 'ST' or 'IO' or some transformer that implements +-- @PrimMonad@. -- --- >>> :{ --- instance (s ~ PrimState m, PrimMonad m) => MonadRandom MWC.Gen s m where --- newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } --- thawGen = fmap MWC.restore unFrozen --- freezeGen = fmap Frozen . MWC.save --- uniformWord8 = MWC.uniform --- uniformWord16 = MWC.uniform --- uniformWord32 = MWC.uniform --- uniformWord64 = MWC.uniform --- uniformShortByteString n g = unsafeSTToPrim (genShortByteStringST n (MWC.uniform g)) --- :} -- | 'RandomGen' is an interface to pure pseudo-random number generators. -- @@ -597,6 +408,28 @@ class Monad m => MonadRandom g s m | g m -> s where uniformShortByteString n = genShortByteStringIO n . uniformWord64 {-# INLINE uniformShortByteString #-} +------------------------------------------------------------------------------- +-- Monadic adapters +------------------------------------------------------------------------------- + +-- $monadicadapters +-- +-- Pure pseudo-random number generators can be used in monadic code via the +-- adapters 'PureGen', 'AtomicGen', 'IOGen' and 'STGen'. +-- +-- * 'PureGen' can be used in any state monad. With strict 'StateT' there is +-- no performance overhead compared to using the 'RandomGen' instance +-- directly. 'PureGen' is /not/ safe to use in the presence of exceptions +-- and concurrency. +-- +-- * 'AtomicGen' is safe in the presence of exceptions and concurrency since +-- it performs all actions atomically. +-- +-- * 'IOGen' is a wrapper around an 'IORef' that holds a pure generator. +-- 'IOGen' is safe in the presence of exceptions, but not concurrency. +-- +-- * 'STGen' is a wrapper around an 'STRef' that holds a pure generator. +-- 'STGen' is safe in the presence of exceptions, but not concurrency. -- | Interface to operations on 'RandomGen' wrappers like 'IOGen' and 'PureGen'. -- @@ -1617,16 +1450,19 @@ instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e, Uniform f) => U instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e, Uniform f, Uniform g) => Uniform (a, b, c, d, e, f, g) where uniform g = (,,,,,,) <$> uniform g <*> uniform g <*> uniform g <*> uniform g <*> uniform g <*> uniform g <*> uniform g --- The global pseudo-random number generator - -{- $globalrng #globalrng# +------------------------------------------------------------------------------- +-- Global pseudo-random number generator +------------------------------------------------------------------------------- -There is a single, implicit, global pseudo-random number generator of type -'StdGen', held in some global variable maintained by the 'IO' monad. It is -initialised automatically in some system-dependent fashion, for example, by -using the time of day, or Linux's kernel pseudo-random number generator. To get -deterministic behaviour, use 'setStdGen'. --} +-- $globalstdgen +-- +-- There is a single, implicit, global pseudo-random number generator of type +-- 'StdGen', held in a global variable maintained by the 'IO' monad. It is +-- initialised automatically in some system-dependent fashion. To get +-- deterministic behaviour, use 'setStdGen'. +-- +-- Note that 'mkStdGen' also gives deterministic behaviour without requiring an +-- 'IO' context. -- |Sets the global pseudo-random number generator. setStdGen :: StdGen -> IO () @@ -1658,15 +1494,182 @@ getStdRandom :: (StdGen -> (a,StdGen)) -> IO a getStdRandom f = atomicModifyIORef' theStdGen (swap . f) where swap (v,g) = (g,v) -{- $references - -1. Guy L. Steele, Jr., Doug Lea, and Christine H. Flood. 2014. Fast splittable -pseudorandom number generators. In Proceedings of the 2014 ACM International -Conference on Object Oriented Programming Systems Languages & Applications -(OOPSLA '14). ACM, New York, NY, USA, 453-472. DOI: - +------------------------------------------------------------------------------- +-- Notes +------------------------------------------------------------------------------- --} +-- $implementrandomgen +-- +-- Consider these points when writing a 'RandomGen' instance for a given pure +-- pseudo-random number generator: +-- +-- * If the pseudo-random number generator has a power-of-2 modulus, that is, +-- it natively outputs @2^n@ bits of randomness for some @n@, implement +-- 'genWord8', 'genWord16', 'genWord32' and 'genWord64'. See below for more +-- details. +-- +-- * If the pseudo-random number generator does not have a power-of-2 +-- modulus, implement 'next' and 'genRange'. See below for more details. +-- +-- * If the pseudo-random number generator is splittable, implement 'split'. +-- If there is no suitable implementation, 'split' should fail with a +-- helpful error message. +-- +-- === How to implement 'RandomGen' for a pseudo-random number generator with power-of-2 modulus +-- +-- Suppose you want to implement a [permuted congruential +-- generator](https://en.wikipedia.org/wiki/Permuted_congruential_generator). +-- +-- >>> data PCGen = PCGen !Word64 !Word64 +-- +-- It produces a full 'Word32' of randomness per iteration. +-- +-- >>> :{ +-- let stepGen :: PCGen -> (Word32, PCGen) +-- stepGen (PCGen state inc) = let +-- newState = state * 6364136223846793005 + (inc .|. 1) +-- xorShifted = fromIntegral (((state `shiftR` 18) `xor` state) `shiftR` 27) :: Word32 +-- rot = fromIntegral (state `shiftR` 59) :: Word32 +-- out = (xorShifted `shiftR` (fromIntegral rot)) .|. (xorShifted `shiftL` fromIntegral ((-rot) .&. 31)) +-- in (out, PCGen newState inc) +-- :} +-- +-- >>> fst $ stepGen $ snd $ stepGen (PCGen 17 29) +-- 3288430965 +-- +-- You can make it an instance of 'RandomGen' as follows: +-- +-- >>> :{ +-- instance RandomGen PCGen where +-- genWord32 = stepGen +-- genWord64 g = (buildWord64 x y, g'') +-- where +-- (x, g') = stepGen g +-- (y, g'') = stepGen g' +-- :} +-- +-- This definition satisfies the compiler. However, the default implementations +-- of 'genWord8' and 'genWord16' are geared towards backwards compatibility +-- with 'RandomGen' instances based on 'next' and 'genRange'. This means that +-- they are not optimal for pseudo-random number generators with a power-of-2 +-- modulo. +-- +-- So let's implement a faster 'RandomGen' instance for our pseudo-random +-- number generator as follows: +-- +-- >>> newtype PCGen' = PCGen' { unPCGen :: PCGen } +-- >>> let stepGen' = second PCGen' . stepGen . unPCGen +-- >>> :{ +-- instance RandomGen PCGen' where +-- genWord8 = first fromIntegral . stepGen' +-- genWord16 = first fromIntegral . stepGen' +-- genWord32 = stepGen' +-- genWord64 g = (buildWord64 x y, g'') +-- where +-- (x, g') = stepGen' g +-- (y, g'') = stepGen' g' +-- :} +-- +-- === How to implement 'RandomGen' for a pseudo-random number generator without a power-of-2 modulus +-- +-- __We do not recommend you implement any new pseudo-random number generators without a power-of-2 modulus.__ +-- +-- Pseudo-random number generators without a power-of-2 modulus perform +-- /significantly worse/ than pseudo-random number generators with a power-of-2 +-- modulus with this library. This is because most functionality in this +-- library is based on generating and transforming uniformly pseudo-random +-- machine words, and generating uniformly pseudo-random machine words using a +-- pseudo-random number generator without a power-of-2 modulus is expensive. +-- +-- The pseudo-random number generator from +-- natively +-- generates an integer value in the range @[1, 2147483562]@. This is the +-- generator used by this library before it was replaced by SplitMix in version +-- 1.2. +-- +-- >>> data LegacyGen = LegacyGen !Int32 !Int32 +-- >>> :{ +-- let legacyNext :: LegacyGen -> (Int, LegacyGen) +-- legacyNext (LegacyGen s1 s2) = (fromIntegral z', LegacyGen s1'' s2'') where +-- z' = if z < 1 then z + 2147483562 else z +-- z = s1'' - s2'' +-- k = s1 `quot` 53668 +-- s1' = 40014 * (s1 - k * 53668) - k * 12211 +-- s1'' = if s1' < 0 then s1' + 2147483563 else s1' +-- k' = s2 `quot` 52774 +-- s2' = 40692 * (s2 - k' * 52774) - k' * 3791 +-- s2'' = if s2' < 0 then s2' + 2147483399 else s2' +-- :} +-- +-- You can make it an instance of 'RandomGen' as follows: +-- +-- >>> :{ +-- instance RandomGen LegacyGen where +-- next = legacyNext +-- genRange _ = (1, 2147483562) +-- :} +-- +-- $implementmonadrandom +-- +-- Typically, a monadic pseudo-random number generator has facilities to save +-- and restore its internal state in addition to generating pseudo-random +-- pseudo-random numbers. +-- +-- Here is an example instance for the monadic pseudo-random number generator +-- from the @mwc-random@ package: +-- +-- > instance (s ~ PrimState m, PrimMonad m) => MonadRandom MWC.Gen s m where +-- > newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } +-- > thawGen = fmap MWC.restore unFrozen +-- > freezeGen = fmap Frozen . MWC.save +-- > uniformWord8 = MWC.uniform +-- > uniformWord16 = MWC.uniform +-- > uniformWord32 = MWC.uniform +-- > uniformWord64 = MWC.uniform +-- > uniformShortByteString n g = unsafeSTToPrim (genShortByteStringST n (MWC.uniform g)) +-- +-- $deprecations +-- +-- Version 1.2 mostly maintains backwards compatibility with version 1.1. This +-- has a few consequences users should be aware of: +-- +-- * The type class 'Random' is deprecated and only provided for backwards +-- compatibility. New code should use 'Uniform' and 'UniformRange' instead. +-- +-- * The methods 'next' and 'genRange' in 'RandomGen' are deprecated and only +-- provided for backwards compatibility. New instances of 'RandomGen' should +-- implement word-based methods instead. See below for more information +-- about how to write a 'RandomGen' instance. +-- +-- * This library provides instances for 'Random' for some unbounded datatypes +-- for backwards compatibility. For an unbounded datatype, there is no way +-- to generate a value with uniform probability out of its entire domain, so +-- the 'random' implementation for unbounded datatypes actually generates a +-- value based on some fixed range. +-- +-- For 'Integer', 'random' generates a value in the 'Int' range. For 'Float' +-- and 'Double', 'random' generates a floating point value in the range @[0, +-- 1)@. +-- +-- This library does not provide 'Uniform' instances for any unbounded +-- datatypes. +-- +-- $reproducibility +-- +-- If you have two builds of a particular piece of code against this library, +-- any deterministic function call should give the same result in the two +-- builds if the builds are +-- +-- * compiled against the same major version of this library +-- * on the same architecture (32-bit or 64-bit) +-- +-- $references +-- +-- 1. Guy L. Steele, Jr., Doug Lea, and Christine H. Flood. 2014. Fast +-- splittable pseudorandom number generators. In Proceedings of the 2014 ACM +-- International Conference on Object Oriented Programming Systems Languages & +-- Applications (OOPSLA '14). ACM, New York, NY, USA, 453-472. DOI: +-- -- Appendix 1. -- @@ -1732,3 +1735,38 @@ Conference on Object Oriented Programming Systems Languages & Applications -- --^ top - bottom <= -1 - (-2^(n-1)) = 2^(n-1) - 1 -- --^ 0 < top - bottom <= 2^(n-1) - 1 -- = top - bottom + +-- $setup +-- >>> import Control.Arrow (first, second) +-- >>> import Control.Monad (replicateM) +-- >>> import Control.Monad.Primitive +-- >>> import Data.Bits +-- >>> import Data.Int (Int32) +-- >>> import Data.Word (Word8, Word16, Word32, Word64) +-- >>> import System.IO (IOMode(WriteMode), withBinaryFile) +-- >>> import qualified System.Random.MWC as MWC +-- +-- >>> :set -XFlexibleContexts +-- >>> :set -XFlexibleInstances +-- >>> :set -XMultiParamTypeClasses +-- >>> :set -XTypeFamilies +-- >>> :set -XUndecidableInstances +-- +-- >>> :set -fno-warn-missing-methods +-- +-- >>> :{ +-- let buildWord64 :: Word32 -> Word32 -> Word64 +-- buildWord64 x y = (fromIntegral x `shiftL` 32) .|. fromIntegral y +-- :} +-- +-- >>> :{ +-- instance (s ~ PrimState m, PrimMonad m) => MonadRandom MWC.Gen s m where +-- newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } +-- thawGen = fmap MWC.restore unFrozen +-- freezeGen = fmap Frozen . MWC.save +-- uniformWord8 = MWC.uniform +-- uniformWord16 = MWC.uniform +-- uniformWord32 = MWC.uniform +-- uniformWord64 = MWC.uniform +-- uniformShortByteString n g = unsafeSTToPrim (genShortByteStringST n (MWC.uniform g)) +-- :} From d7c314ce77bacaaa70ade4f6e3dc98440d63804d Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Wed, 15 Apr 2020 15:42:33 +0200 Subject: [PATCH 131/170] Fix doctest --- System/Random.hs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index e0501f1a1..ec736cab6 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -177,10 +177,8 @@ import GHC.IO (IO(..)) -- As an example, @rolls@ generates @n@ pseudo-random values of @Word8@ in the -- range @[1, 6]@. -- --- >>> :{ --- let rolls :: MonadRandom g s m => Int -> g s -> m [Word8] --- rolls n = replicateM n . uniformR (1, 6) --- :} +-- > rolls :: MonadRandom g s m => Int -> g s -> m [Word8] +-- > rolls n = replicateM n . uniformR (1, 6) -- -- Given a /monadic/ pseudo-random number generator, you can run this -- probabilistic computation as follows: @@ -1770,3 +1768,8 @@ getStdRandom f = atomicModifyIORef' theStdGen (swap . f) -- uniformWord64 = MWC.uniform -- uniformShortByteString n g = unsafeSTToPrim (genShortByteStringST n (MWC.uniform g)) -- :} +-- +-- >>> :{ +-- let rolls :: MonadRandom g s m => Int -> g s -> m [Word8] +-- rolls n = replicateM n . uniformR (1, 6) +-- :} From 986cd5edd54370b5cf6de0ef213dd76de1f1e379 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Tue, 14 Apr 2020 21:34:43 +0300 Subject: [PATCH 132/170] Add RandomGen instance for 32bit version of splitmix --- System/Random.hs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/System/Random.hs b/System/Random.hs index 975b96de7..793860ef0 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -382,6 +382,7 @@ import GHC.Exts import GHC.ForeignPtr import System.IO.Unsafe (unsafePerformIO) import qualified System.Random.SplitMix as SM +import qualified System.Random.SplitMix32 as SM32 import GHC.Word import GHC.IO (IO(..)) @@ -950,6 +951,12 @@ instance RandomGen StdGen where genWord64 = SM.nextWord64 split = SM.splitSMGen +instance RandomGen SM32.SMGen where + next = SM32.nextInt + genWord32 = SM32.nextWord32 + genWord64 = SM32.nextWord64 + split = SM32.splitSMGen + -- | Constructs a 'StdGen' deterministically. mkStdGen :: Int -> StdGen mkStdGen s = SM.mkSMGen $ fromIntegral s From b70d50d6b6cafd08be08c4ac68a63d5254fc9740 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Tue, 14 Apr 2020 20:15:29 +0300 Subject: [PATCH 133/170] Addition of pure version for Uniform and UniformRange: * Rename monadic versions: * `uniform` -> `uniformM` * `uniformR` -> `uniformRM` * Add pure `uniform` and `uniformR` that use `PureGen` for implementation --- System/Random.hs | 349 ++++++++++++++++++++------------------------ tests/Spec/Range.hs | 4 +- tests/Spec/Run.hs | 8 +- 3 files changed, 168 insertions(+), 193 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index ec736cab6..a84d434cd 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -85,6 +85,8 @@ module System.Random -- * Pseudo-random values of various types -- $uniform , Uniform(..) + , uniform + , uniformR , uniformListM , UniformRange(..) , Random(..) @@ -172,7 +174,7 @@ import GHC.IO (IO(..)) -- $usagemonadic -- -- In monadic code, use the relevant 'Uniform' and 'UniformRange' instances to --- generate pseudo-random values via 'uniform' and 'uniformR', respectively. +-- generate pseudo-random values via 'uniformM' and 'uniformRM', respectively. -- -- As an example, @rolls@ generates @n@ pseudo-random values of @Word8@ in the -- range @[1, 6]@. @@ -242,7 +244,7 @@ class RandomGen g where -- [here](https://alexey.kuleshevi.ch/blog/2019/12/21/random-benchmarks) for -- more details. It is thus deprecated. next :: g -> (Int, g) - next g = runGenState g (uniformR (genRange g)) + next g = runGenState g (uniformRM (genRange g)) -- | Returns a 'Word8' that is uniformly distributed over the entire 'Word8' -- range. @@ -478,7 +480,7 @@ runGenM_ fg action = fst <$> runGenM fg action -- -- @since 1.2 uniformListM :: (MonadRandom g s m, Uniform a) => g s -> Int -> m [a] -uniformListM gen n = replicateM n (uniform gen) +uniformListM gen n = replicateM n (uniformM gen) data MBA s = MBA (MutableByteArray# s) @@ -589,11 +591,24 @@ instance (RandomGen g, MonadState g m) => MonadRandom (PureGen g) g m where uniformWord64 _ = state genWord64 uniformShortByteString n _ = state (genShortByteString n) +-- | Pure version of `uniformM` that works with instances of `RandomGen` +-- +-- @since 1.2 +uniform :: (RandomGen g, Uniform a) => g -> (a, g) +uniform g = runGenState g uniformM + + +-- | Pure version of `uniformRM` that works with instances of `RandomGen` +-- +-- @since 1.2 +uniformR :: (RandomGen g, UniformRange a) => g -> (a, a) -> (a, g) +uniformR g r = runGenState g (uniformRM r) + -- | Generates a pseudo-random value in a state monad. -- -- @since 1.2 genRandom :: (RandomGen g, Random a, MonadState g m) => PureGen g g -> m a -genRandom = randomM +genRandom _ = state random -- | Splits a pseudo-random number generator into two. Updates the state with -- one of the resulting generators and returns the other. @@ -682,7 +697,7 @@ applyAtomicGen op (AtomicGenI gVar) = -- -- >>> import UnliftIO.Temporary (withSystemTempFile) -- >>> import Data.ByteString (hPutStr) --- >>> let ioGen g = withSystemTempFile "foo.bin" $ \_ h -> uniformR (0, 100) g >>= flip uniformByteString g >>= hPutStr h +-- >>> let ioGen g = withSystemTempFile "foo.bin" $ \_ h -> uniformRM (0, 100) g >>= flip uniformByteString g >>= hPutStr h -- -- and then run it: -- @@ -818,7 +833,7 @@ mkStdGen s = SM.mkSMGen $ fromIntegral s -- -- @since 1.2 class Uniform a where - uniform :: MonadRandom g s m => g s -> m a + uniformM :: MonadRandom g s m => g s -> m a -- | Generates a value uniformly distributed over the provided inclusive range. -- @@ -830,11 +845,11 @@ class Uniform a where -- -- The following law should hold to make the function always defined: -- --- > uniformR (a,b) = uniform (b,a) +-- > uniformRM (a,b) = uniformM (b,a) -- -- @since 1.2 class UniformRange a where - uniformR :: MonadRandom g s m => (a, a) -> g s -> m a + uniformRM :: MonadRandom g s m => (a, a) -> g s -> m a {- | With a source of pseudo-random number supply in hand, the 'Random' class allows @@ -843,8 +858,8 @@ the programmer to extract pseudo-random values of a variety of types. Minimal complete definition: 'randomR' and 'random'. -} -{-# DEPRECATED randomRIO "In favor of `uniformR`" #-} -{-# DEPRECATED randomIO "In favor of `uniform`" #-} +{-# DEPRECATED randomRIO "In favor of `uniformRM`" #-} +{-# DEPRECATED randomIO "In favor of `uniformRM`" #-} class Random a where -- | Takes a range /(lo,hi)/ and a pseudo-random number generator @@ -856,7 +871,7 @@ class Random a where {-# INLINE randomR #-} randomR :: RandomGen g => (a, a) -> g -> (a, g) default randomR :: (RandomGen g, UniformRange a) => (a, a) -> g -> (a, g) - randomR r g = runGenState g (uniformR r) + randomR r g = runGenState g (uniformRM r) -- | The same as 'randomR', but using a default range determined by the type: -- @@ -869,10 +884,8 @@ class Random a where -- * For 'Integer', the range is (arbitrarily) the range of 'Int'. {-# INLINE random #-} random :: RandomGen g => g -> (a, g) - random g = runGenState g genRandom - - --{-# INLINE randomM #-} - randomM :: MonadRandom g s m => g s -> m a + default random :: (RandomGen g, Uniform a) => g -> (a, g) + random g = runGenState g uniformM -- | Plural variant of 'randomR', producing an infinite list of -- pseudo-random values instead of returning a new generator. @@ -911,254 +924,221 @@ buildRandoms cons rand = go -- Generate values in the Int range instance Random Integer where random = first (toInteger :: Int -> Integer) . random - randomM = fmap (toInteger :: Int -> Integer) . randomM instance UniformRange Integer where - uniformR = uniformIntegerM + uniformRM = uniformIntegerM -instance Random Int8 where - randomM = uniform +instance Random Int8 instance Uniform Int8 where - uniform = fmap (fromIntegral :: Word8 -> Int8) . uniformWord8 + uniformM = fmap (fromIntegral :: Word8 -> Int8) . uniformWord8 instance UniformRange Int8 where - uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int8 -> Word8) fromIntegral + uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int8 -> Word8) fromIntegral -instance Random Int16 where - randomM = uniform +instance Random Int16 instance Uniform Int16 where - uniform = fmap (fromIntegral :: Word16 -> Int16) . uniformWord16 + uniformM = fmap (fromIntegral :: Word16 -> Int16) . uniformWord16 instance UniformRange Int16 where - uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int16 -> Word16) fromIntegral + uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int16 -> Word16) fromIntegral -instance Random Int32 where - randomM = uniform +instance Random Int32 instance Uniform Int32 where - uniform = fmap (fromIntegral :: Word32 -> Int32) . uniformWord32 + uniformM = fmap (fromIntegral :: Word32 -> Int32) . uniformWord32 instance UniformRange Int32 where - uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int32 -> Word32) fromIntegral + uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int32 -> Word32) fromIntegral -instance Random Int64 where - randomM = uniform +instance Random Int64 instance Uniform Int64 where - uniform = fmap (fromIntegral :: Word64 -> Int64) . uniformWord64 + uniformM = fmap (fromIntegral :: Word64 -> Int64) . uniformWord64 instance UniformRange Int64 where - uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int64 -> Word64) fromIntegral + uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int64 -> Word64) fromIntegral -instance Random Int where - randomM = uniform +instance Random Int instance Uniform Int where #if WORD_SIZE_IN_BITS < 64 - uniform = fmap (fromIntegral :: Word32 -> Int) . uniformWord32 + uniformM = fmap (fromIntegral :: Word32 -> Int) . uniformWord32 #else - uniform = fmap (fromIntegral :: Word64 -> Int) . uniformWord64 + uniformM = fmap (fromIntegral :: Word64 -> Int) . uniformWord64 #endif - {-# INLINE uniform #-} + {-# INLINE uniformM #-} instance UniformRange Int where - uniformR = signedBitmaskWithRejectionRM (fromIntegral :: Int -> Word) fromIntegral - {-# INLINE uniformR #-} + uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int -> Word) fromIntegral + {-# INLINE uniformRM #-} -instance Random Word where - randomM = uniform +instance Random Word instance Uniform Word where #if WORD_SIZE_IN_BITS < 64 - uniform = fmap (fromIntegral :: Word32 -> Word) . uniformWord32 + uniformM = fmap (fromIntegral :: Word32 -> Word) . uniformWord32 #else - uniform = fmap (fromIntegral :: Word64 -> Word) . uniformWord64 + uniformM = fmap (fromIntegral :: Word64 -> Word) . uniformWord64 #endif instance UniformRange Word where - {-# INLINE uniformR #-} - uniformR = unsignedBitmaskWithRejectionRM + {-# INLINE uniformRM #-} + uniformRM = unsignedBitmaskWithRejectionRM -instance Random Word8 where - randomM = uniform +instance Random Word8 instance Uniform Word8 where - {-# INLINE uniform #-} - uniform = uniformWord8 + {-# INLINE uniformM #-} + uniformM = uniformWord8 instance UniformRange Word8 where - {-# INLINE uniformR #-} - uniformR = unsignedBitmaskWithRejectionRM + {-# INLINE uniformRM #-} + uniformRM = unsignedBitmaskWithRejectionRM -instance Random Word16 where - randomM = uniform +instance Random Word16 instance Uniform Word16 where - {-# INLINE uniform #-} - uniform = uniformWord16 + {-# INLINE uniformM #-} + uniformM = uniformWord16 instance UniformRange Word16 where - {-# INLINE uniformR #-} - uniformR = unsignedBitmaskWithRejectionRM + {-# INLINE uniformRM #-} + uniformRM = unsignedBitmaskWithRejectionRM -instance Random Word32 where - randomM = uniform +instance Random Word32 instance Uniform Word32 where - {-# INLINE uniform #-} - uniform = uniformWord32 + {-# INLINE uniformM #-} + uniformM = uniformWord32 instance UniformRange Word32 where - {-# INLINE uniformR #-} - uniformR (b, t) g | b > t = (+t) <$> unbiasedWordMult32 (b - t) g + {-# INLINE uniformRM #-} + uniformRM (b, t) g | b > t = (+t) <$> unbiasedWordMult32 (b - t) g | otherwise = (+b) <$> unbiasedWordMult32 (t - b) g -instance Random Word64 where - randomM = uniform +instance Random Word64 instance Uniform Word64 where - {-# INLINE uniform #-} - uniform = uniformWord64 + {-# INLINE uniformM #-} + uniformM = uniformWord64 instance UniformRange Word64 where - {-# INLINE uniformR #-} - uniformR = unsignedBitmaskWithRejectionRM + {-# INLINE uniformRM #-} + uniformRM = unsignedBitmaskWithRejectionRM -instance Random CBool where - randomM = uniform +instance Random CBool instance Uniform CBool where - uniform = fmap CBool . uniform + uniformM = fmap CBool . uniformM instance UniformRange CBool where - uniformR (CBool b, CBool t) = fmap CBool . uniformR (b, t) + uniformRM (CBool b, CBool t) = fmap CBool . uniformRM (b, t) -instance Random CChar where - randomM = uniform +instance Random CChar instance Uniform CChar where - uniform = fmap CChar . uniform + uniformM = fmap CChar . uniformM instance UniformRange CChar where - uniformR (CChar b, CChar t) = fmap CChar . uniformR (b, t) + uniformRM (CChar b, CChar t) = fmap CChar . uniformRM (b, t) -instance Random CSChar where - randomM = uniform +instance Random CSChar instance Uniform CSChar where - uniform = fmap CSChar . uniform + uniformM = fmap CSChar . uniformM instance UniformRange CSChar where - uniformR (CSChar b, CSChar t) = fmap CSChar . uniformR (b, t) + uniformRM (CSChar b, CSChar t) = fmap CSChar . uniformRM (b, t) -instance Random CUChar where - randomM = uniform +instance Random CUChar instance Uniform CUChar where - uniform = fmap CUChar . uniform + uniformM = fmap CUChar . uniformM instance UniformRange CUChar where - uniformR (CUChar b, CUChar t) = fmap CUChar . uniformR (b, t) + uniformRM (CUChar b, CUChar t) = fmap CUChar . uniformRM (b, t) -instance Random CShort where - randomM = uniform +instance Random CShort instance Uniform CShort where - uniform = fmap CShort . uniform + uniformM = fmap CShort . uniformM instance UniformRange CShort where - uniformR (CShort b, CShort t) = fmap CShort . uniformR (b, t) + uniformRM (CShort b, CShort t) = fmap CShort . uniformRM (b, t) -instance Random CUShort where - randomM = uniform +instance Random CUShort instance Uniform CUShort where - uniform = fmap CUShort . uniform + uniformM = fmap CUShort . uniformM instance UniformRange CUShort where - uniformR (CUShort b, CUShort t) = fmap CUShort . uniformR (b, t) + uniformRM (CUShort b, CUShort t) = fmap CUShort . uniformRM (b, t) -instance Random CInt where - randomM = uniform +instance Random CInt instance Uniform CInt where - uniform = fmap CInt . uniform + uniformM = fmap CInt . uniformM instance UniformRange CInt where - uniformR (CInt b, CInt t) = fmap CInt . uniformR (b, t) + uniformRM (CInt b, CInt t) = fmap CInt . uniformRM (b, t) -instance Random CUInt where - randomM = uniform +instance Random CUInt instance Uniform CUInt where - uniform = fmap CUInt . uniform + uniformM = fmap CUInt . uniformM instance UniformRange CUInt where - uniformR (CUInt b, CUInt t) = fmap CUInt . uniformR (b, t) + uniformRM (CUInt b, CUInt t) = fmap CUInt . uniformRM (b, t) -instance Random CLong where - randomM = uniform +instance Random CLong instance Uniform CLong where - uniform = fmap CLong . uniform + uniformM = fmap CLong . uniformM instance UniformRange CLong where - uniformR (CLong b, CLong t) = fmap CLong . uniformR (b, t) + uniformRM (CLong b, CLong t) = fmap CLong . uniformRM (b, t) -instance Random CULong where - randomM = uniform +instance Random CULong instance Uniform CULong where - uniform = fmap CULong . uniform + uniformM = fmap CULong . uniformM instance UniformRange CULong where - uniformR (CULong b, CULong t) = fmap CULong . uniformR (b, t) + uniformRM (CULong b, CULong t) = fmap CULong . uniformRM (b, t) -instance Random CPtrdiff where - randomM = uniform +instance Random CPtrdiff instance Uniform CPtrdiff where - uniform = fmap CPtrdiff . uniform + uniformM = fmap CPtrdiff . uniformM instance UniformRange CPtrdiff where - uniformR (CPtrdiff b, CPtrdiff t) = fmap CPtrdiff . uniformR (b, t) + uniformRM (CPtrdiff b, CPtrdiff t) = fmap CPtrdiff . uniformRM (b, t) -instance Random CSize where - randomM = uniform +instance Random CSize instance Uniform CSize where - uniform = fmap CSize . uniform + uniformM = fmap CSize . uniformM instance UniformRange CSize where - uniformR (CSize b, CSize t) = fmap CSize . uniformR (b, t) + uniformRM (CSize b, CSize t) = fmap CSize . uniformRM (b, t) -instance Random CWchar where - randomM = uniform +instance Random CWchar instance Uniform CWchar where - uniform = fmap CWchar . uniform + uniformM = fmap CWchar . uniformM instance UniformRange CWchar where - uniformR (CWchar b, CWchar t) = fmap CWchar . uniformR (b, t) + uniformRM (CWchar b, CWchar t) = fmap CWchar . uniformRM (b, t) -instance Random CSigAtomic where - randomM = uniform +instance Random CSigAtomic instance Uniform CSigAtomic where - uniform = fmap CSigAtomic . uniform + uniformM = fmap CSigAtomic . uniformM instance UniformRange CSigAtomic where - uniformR (CSigAtomic b, CSigAtomic t) = fmap CSigAtomic . uniformR (b, t) + uniformRM (CSigAtomic b, CSigAtomic t) = fmap CSigAtomic . uniformRM (b, t) -instance Random CLLong where - randomM = uniform +instance Random CLLong instance Uniform CLLong where - uniform = fmap CLLong . uniform + uniformM = fmap CLLong . uniformM instance UniformRange CLLong where - uniformR (CLLong b, CLLong t) = fmap CLLong . uniformR (b, t) + uniformRM (CLLong b, CLLong t) = fmap CLLong . uniformRM (b, t) -instance Random CULLong where - randomM = uniform +instance Random CULLong instance Uniform CULLong where - uniform = fmap CULLong . uniform + uniformM = fmap CULLong . uniformM instance UniformRange CULLong where - uniformR (CULLong b, CULLong t) = fmap CULLong . uniformR (b, t) + uniformRM (CULLong b, CULLong t) = fmap CULLong . uniformRM (b, t) -instance Random CIntPtr where - randomM = uniform +instance Random CIntPtr instance Uniform CIntPtr where - uniform = fmap CIntPtr . uniform + uniformM = fmap CIntPtr . uniformM instance UniformRange CIntPtr where - uniformR (CIntPtr b, CIntPtr t) = fmap CIntPtr . uniformR (b, t) + uniformRM (CIntPtr b, CIntPtr t) = fmap CIntPtr . uniformRM (b, t) -instance Random CUIntPtr where - randomM = uniform +instance Random CUIntPtr instance Uniform CUIntPtr where - uniform = fmap CUIntPtr . uniform + uniformM = fmap CUIntPtr . uniformM instance UniformRange CUIntPtr where - uniformR (CUIntPtr b, CUIntPtr t) = fmap CUIntPtr . uniformR (b, t) + uniformRM (CUIntPtr b, CUIntPtr t) = fmap CUIntPtr . uniformRM (b, t) -instance Random CIntMax where - randomM = uniform +instance Random CIntMax instance Uniform CIntMax where - uniform = fmap CIntMax . uniform + uniformM = fmap CIntMax . uniformM instance UniformRange CIntMax where - uniformR (CIntMax b, CIntMax t) = fmap CIntMax . uniformR (b, t) + uniformRM (CIntMax b, CIntMax t) = fmap CIntMax . uniformRM (b, t) -instance Random CUIntMax where - randomM = uniform +instance Random CUIntMax instance Uniform CUIntMax where - uniform = fmap CUIntMax . uniform + uniformM = fmap CUIntMax . uniformM instance UniformRange CUIntMax where - uniformR (CUIntMax b, CUIntMax t) = fmap CUIntMax . uniformR (b, t) + uniformRM (CUIntMax b, CUIntMax t) = fmap CUIntMax . uniformRM (b, t) instance Random CFloat where randomR (CFloat l, CFloat h) = first CFloat . randomR (l, h) random = first CFloat . random - randomM = fmap CFloat . randomM instance UniformRange CFloat where - uniformR (CFloat l, CFloat h) = fmap CFloat . uniformR (l, h) + uniformRM (CFloat l, CFloat h) = fmap CFloat . uniformRM (l, h) instance Random CDouble where randomR (CDouble l, CDouble h) = first CDouble . randomR (l, h) random = first CDouble . random - randomM = fmap CDouble . randomM instance UniformRange CDouble where - uniformR (CDouble l, CDouble h) = fmap CDouble . uniformR (l, h) + uniformRM (CDouble l, CDouble h) = fmap CDouble . uniformRM (l, h) -- The `chr#` and `ord#` are the prim functions that will be called, regardless of which @@ -1175,34 +1155,30 @@ charToWord32 :: Char -> Word32 charToWord32 (C# c#) = W32# (int2Word# (ord# c#)) {-# INLINE charToWord32 #-} -instance Random Char where - randomM = uniform - {-# INLINE randomM #-} +instance Random Char instance Uniform Char where - uniform g = word32ToChar <$> unsignedBitmaskWithRejectionM uniform (charToWord32 maxBound) g - {-# INLINE uniform #-} + uniformM g = word32ToChar <$> unsignedBitmaskWithRejectionM uniformM (charToWord32 maxBound) g + {-# INLINE uniformM #-} instance UniformRange Char where - uniformR (l, h) g = + uniformRM (l, h) g = word32ToChar <$> unsignedBitmaskWithRejectionRM (charToWord32 l, charToWord32 h) g - {-# INLINE uniformR #-} + {-# INLINE uniformRM #-} -instance Random Bool where - randomM = uniform +instance Random Bool instance Uniform Bool where - uniform = fmap wordToBool . uniformWord8 + uniformM = fmap wordToBool . uniformWord8 where wordToBool w = (w .&. 1) /= 0 instance UniformRange Bool where - uniformR (False, False) _g = return False - uniformR (True, True) _g = return True - uniformR _ g = uniform g + uniformRM (False, False) _g = return False + uniformRM (True, True) _g = return True + uniformRM _ g = uniformM g instance Random Double where - randomR r g = runGenState g (uniformR r) - random g = runGenState g randomM - randomM = uniformR (0, 1) + randomR r g = runGenState g (uniformRM r) + random g = runGenState g (uniformRM (0, 1)) instance UniformRange Double where - uniformR (l, h) g = do + uniformRM (l, h) g = do w64 <- uniformWord64 g let x = word64ToDoubleInUnitInterval w64 return $ (h - l) * x + l @@ -1238,11 +1214,10 @@ foreign import prim "stg_word64ToDoubleyg" instance Random Float where - randomR r g = runGenState g (uniformR r) - random g = runGenState g randomM - randomM = uniformR (0, 1) + randomR r g = runGenState g (uniformRM r) + random g = runGenState g (uniformRM (0, 1)) instance UniformRange Float where - uniformR (l, h) g = do + uniformRM (l, h) g = do w32 <- uniformWord32 g let x = word32ToFloatInUnitInterval w32 return $ (h - l) * x + l @@ -1352,7 +1327,7 @@ uniformIntegerWords n gen = go 0 n go !acc i | i == 0 = return acc | otherwise = do - (w :: Word) <- uniform gen + (w :: Word) <- uniformM gen go ((acc `shiftL` WORD_SIZE_IN_BITS) .|. (fromIntegral w)) (i - 1) {-# INLINE uniformIntegerWords #-} @@ -1391,7 +1366,7 @@ unsignedBitmaskWithRejectionRM :: unsignedBitmaskWithRejectionRM (bottom, top) gen | bottom > top = unsignedBitmaskWithRejectionRM (top, bottom) gen | bottom == top = pure top - | otherwise = (bottom +) <$> unsignedBitmaskWithRejectionM uniform range gen + | otherwise = (bottom +) <$> unsignedBitmaskWithRejectionM uniformM range gen where range = top - bottom {-# INLINE unsignedBitmaskWithRejectionRM #-} @@ -1408,18 +1383,18 @@ signedBitmaskWithRejectionRM toUnsigned fromUnsigned (bottom, top) gen | bottom > top = signedBitmaskWithRejectionRM toUnsigned fromUnsigned (top, bottom) gen | bottom == top = pure top | otherwise = (bottom +) . fromUnsigned <$> - unsignedBitmaskWithRejectionM uniform range gen + unsignedBitmaskWithRejectionM uniformM range gen where -- This works in all cases, see Appendix 1 at the end of the file. range = toUnsigned top - toUnsigned bottom {-# INLINE signedBitmaskWithRejectionRM #-} unsignedBitmaskWithRejectionM :: (Ord a, FiniteBits a, Num a, MonadRandom g s m) => (g s -> m a) -> a -> g s -> m a -unsignedBitmaskWithRejectionM genUniform range gen = go +unsignedBitmaskWithRejectionM genUniformM range gen = go where mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) go = do - x <- genUniform gen + x <- genUniformM gen let x' = x .&. mask if x' > range then go @@ -1431,22 +1406,22 @@ unsignedBitmaskWithRejectionM genUniform range gen = go ------------------------------------------------------------------------------- instance (Uniform a, Uniform b) => Uniform (a, b) where - uniform g = (,) <$> uniform g <*> uniform g + uniformM g = (,) <$> uniformM g <*> uniformM g instance (Uniform a, Uniform b, Uniform c) => Uniform (a, b, c) where - uniform g = (,,) <$> uniform g <*> uniform g <*> uniform g + uniformM g = (,,) <$> uniformM g <*> uniformM g <*> uniformM g instance (Uniform a, Uniform b, Uniform c, Uniform d) => Uniform (a, b, c, d) where - uniform g = (,,,) <$> uniform g <*> uniform g <*> uniform g <*> uniform g + uniformM g = (,,,) <$> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e) => Uniform (a, b, c, d, e) where - uniform g = (,,,,) <$> uniform g <*> uniform g <*> uniform g <*> uniform g <*> uniform g + uniformM g = (,,,,) <$> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e, Uniform f) => Uniform (a, b, c, d, e, f) where - uniform g = (,,,,,) <$> uniform g <*> uniform g <*> uniform g <*> uniform g <*> uniform g <*> uniform g + uniformM g = (,,,,,) <$> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e, Uniform f, Uniform g) => Uniform (a, b, c, d, e, f, g) where - uniform g = (,,,,,,) <$> uniform g <*> uniform g <*> uniform g <*> uniform g <*> uniform g <*> uniform g <*> uniform g + uniformM g = (,,,,,,) <$> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g ------------------------------------------------------------------------------- -- Global pseudo-random number generator @@ -1771,5 +1746,5 @@ getStdRandom f = atomicModifyIORef' theStdGen (swap . f) -- -- >>> :{ -- let rolls :: MonadRandom g s m => Int -> g s -> m [Word8] --- rolls n = replicateM n . uniformR (1, 6) +-- rolls n = replicateM n . uniformRM (1, 6) -- :} diff --git a/tests/Spec/Range.hs b/tests/Spec/Range.hs index 4dd6aebe5..8a3c40ffc 100644 --- a/tests/Spec/Range.hs +++ b/tests/Spec/Range.hs @@ -26,9 +26,9 @@ singleton g x = result == x uniformRangeWithin :: (RandomGen g, UniformRange a, Ord a) => g -> (a, a) -> Bool uniformRangeWithin gen (l, r) = runGenState_ gen $ \g -> - (\result -> min l r <= result && result <= max l r) <$> uniformR (l, r) g + (\result -> min l r <= result && result <= max l r) <$> uniformRM (l, r) g uniformRangeWithinExcluded :: (RandomGen g, UniformRange a, Ord a) => g -> (a, a) -> Bool uniformRangeWithinExcluded gen (l, r) = runGenState_ gen $ \g -> - (\result -> min l r <= result && (l == r || result < max l r)) <$> uniformR (l, r) g + (\result -> min l r <= result && (l == r || result < max l r)) <$> uniformRM (l, r) g diff --git a/tests/Spec/Run.hs b/tests/Spec/Run.hs index a43e3fb34..24f48fba1 100644 --- a/tests/Spec/Run.hs +++ b/tests/Spec/Run.hs @@ -5,8 +5,8 @@ import System.Random runsEqual :: RandomGen g => g -> IO Bool runsEqual g = do - let pureResult = runGenState_ g uniform :: Word64 - stResult = runSTGen_ g uniform - ioResult <- runGenM_ (IOGen g) uniform - atomicResult <- runGenM_ (AtomicGen g) uniform + let pureResult = runGenState_ g uniformM :: Word64 + stResult = runSTGen_ g uniformM + ioResult <- runGenM_ (IOGen g) uniformM + atomicResult <- runGenM_ (AtomicGen g) uniformM return $ all (pureResult ==) [stResult, ioResult, atomicResult] From 2a6b4057a6c9da39e32337a90ab464e5dd7e2cc6 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Fri, 17 Apr 2020 22:21:43 +0300 Subject: [PATCH 134/170] Major restructure of the module: * Separate `System.Random` into: * `System.Random.Internal` that hold most of the classes, internal functions and some of the instances * `Sytstem.Random` that export the pure interface only. That means `RandomGen`, `Random`, `Uniform` and `UniformRange` classes * `System.Random.Monad` all of the monadic functionality plus the re-export of the full `System.Random` module. * Split the relevant parts of documentation into Pure and Monadic and place them into their corresponding modules --- System/Random.hs | 1361 +------------------------------------ System/Random/Internal.hs | 962 ++++++++++++++++++++++++++ System/Random/Monad.hs | 480 +++++++++++++ random.cabal | 2 + tests/Spec/Range.hs | 2 +- tests/Spec/Run.hs | 2 +- 6 files changed, 1465 insertions(+), 1344 deletions(-) create mode 100644 System/Random/Internal.hs create mode 100644 System/Random/Monad.hs diff --git a/System/Random.hs b/System/Random.hs index 2cd15fffe..bc6738bb0 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -1,21 +1,5 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE CPP #-} {-# LANGUAGE DefaultSignatures #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE FunctionalDependencies #-} -{-# LANGUAGE GHCForeignImportPrim #-} -{-# LANGUAGE MagicHash #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE Trustworthy #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE UnboxedTuples #-} -{-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE UnliftedFFITypes #-} - -#include "MachDeps.h" -- | -- Module : System.Random @@ -30,23 +14,15 @@ module System.Random -- * Introduction -- $introduction - -- * Usage - -- ** How to generate pseudo-random values in monadic code - -- $usagemonadic - - -- ** How to generate pseudo-random values in pure code - -- $usagepure - - -- * Pure and monadic pseudo-random number generator interfaces + -- * Pure number generator interface -- $interfaces RandomGen(..) - , MonadRandom(..) - , Frozen(..) - , runGenM - , runGenM_ - , RandomGenM(..) - , splitRandomGenM - + , uniform + , uniformR + , genByteString + , Random(..) + , Uniform + , UniformRange -- ** Standard pseudo-random number generator , StdGen , mkStdGen @@ -58,45 +34,6 @@ module System.Random , setStdGen , newStdGen - -- * Monadic adapters for pure pseudo-random number generators - -- $monadicadapters - - -- ** Pure adapter - , PureGen - , splitGen - , genRandom - , runGenState - , runGenState_ - , runGenStateT - , runGenStateT_ - , runPureGenST - -- ** Mutable adapter with atomic operations - , AtomicGen - , applyAtomicGen - -- ** Mutable adapter in 'IO' - , IOGen - , applyIOGen - -- ** Mutable adapter in 'ST' - , STGen - , applySTGen - , runSTGen - , runSTGen_ - - -- * Pseudo-random values of various types - -- $uniform - , Uniform(..) - , uniform - , uniformR - , uniformListM - , UniformRange(..) - , Random(..) - - -- * Generators for sequences of pseudo-random bytes - , genShortByteStringIO - , genShortByteStringST - , uniformByteString - , genByteString - -- * Compatibility and reproducibility -- ** Backwards compatibility and deprecations -- $deprecations @@ -108,42 +45,24 @@ module System.Random -- ** How to implement 'RandomGen' -- $implementrandomgen - -- ** How to implement 'MonadRandom' - -- $implementmonadrandom - -- * References -- $references ) where import Control.Arrow -import Control.Monad.IO.Class -import Control.Monad.ST -import Control.Monad.ST.Unsafe -import Control.Monad.State.Strict -import Data.Bits -import Data.ByteString.Builder.Prim (word64LE) -import Data.ByteString.Builder.Prim.Internal (runF) -import Data.ByteString.Internal (ByteString(PS)) -import Data.ByteString.Short.Internal (ShortByteString(SBS), fromShort) +import Data.ByteString (ByteString) import Data.Int import Data.IORef -import Data.STRef import Data.Word import Foreign.C.Types -import Foreign.Marshal.Alloc (alloca) -import Foreign.Ptr (plusPtr) -import Foreign.Storable (peekByteOff, pokeByteOff) import GHC.Exts -import GHC.ForeignPtr import System.IO.Unsafe (unsafePerformIO) +import System.Random.Internal import qualified System.Random.SplitMix as SM -import qualified System.Random.SplitMix32 as SM32 -import GHC.Word -import GHC.IO (IO(..)) -- $introduction -- --- This library provides type classes and instances for the following concepts: +-- This module provides type classes and instances for the following concepts: -- -- [Pure pseudo-random number generators] 'RandomGen' is an interface to pure -- pseudo-random number generators. @@ -154,59 +73,6 @@ import GHC.IO (IO(..)) -- package. -- Programmers may, of course, supply their own instances of 'RandomGen'. -- --- [Monadic pseudo-random number generators] 'MonadRandom' is an interface to --- monadic pseudo-random number generators. --- --- [Monadic adapters] 'PureGen', 'AtomicGen', 'IOGen' and 'STGen' turn a --- 'RandomGen' instance into a 'MonadRandom' instance. --- --- [Drawing from a range] 'UniformRange' is used to generate a value of a --- datatype uniformly within an inclusive range. --- --- This library provides instances of 'UniformRange' for many common --- numeric datatypes. --- --- [Drawing from the entire domain of a type] 'Uniform' is used to generate a --- value of a datatype uniformly over all possible values of that datatype. --- --- This library provides instances of 'Uniform' for many common bounded --- numeric datatypes. --- --- $usagemonadic --- --- In monadic code, use the relevant 'Uniform' and 'UniformRange' instances to --- generate pseudo-random values via 'uniformM' and 'uniformRM', respectively. --- --- As an example, @rolls@ generates @n@ pseudo-random values of @Word8@ in the --- range @[1, 6]@. --- --- > rolls :: MonadRandom g s m => Int -> g s -> m [Word8] --- > rolls n = replicateM n . uniformR (1, 6) --- --- Given a /monadic/ pseudo-random number generator, you can run this --- probabilistic computation as follows: --- --- >>> monadicGen <- MWC.create --- >>> rolls 10 monadicGen :: IO [Word8] --- [2,3,6,6,4,4,3,1,5,4] --- --- Given a /pure/ pseudo-random number generator, you can run it in an 'IO' or --- 'ST' context by first applying a monadic adapter like 'AtomicGen', 'IOGen' --- or 'STGen' and then running it with 'runGenM'. --- --- >>> let pureGen = mkStdGen 42 --- >>> runGenM_ (IOGen pureGen) (rolls 10) :: IO [Word8] --- [1,1,3,2,4,5,3,4,6,2] --- --- $usagepure --- --- In pure code, use 'runGenState' and its variants to extract the pure --- pseudo-random value from a monadic computation based on a pure pseudo-random --- number generator. --- --- >>> let pureGen = mkStdGen 42 --- >>> runGenState_ pureGen (rolls 10) :: [Word8] --- [1,1,3,2,4,5,3,4,6,2] ------------------------------------------------------------------------------- -- Pseudo-random number generator interfaces @@ -226,371 +92,9 @@ import GHC.IO (IO(..)) -- produced by the two resulting generators are not correlated. See [1] for -- some background on splittable pseudo-random generators. -- --- ['MonadRandom': monadic pseudo-random number generators] These generators --- mutate their own state as they produce pseudo-random values. They --- generally live in 'ST' or 'IO' or some transformer that implements --- @PrimMonad@. --- - --- | 'RandomGen' is an interface to pure pseudo-random number generators. --- --- 'StdGen' is the standard 'RandomGen' instance provided by this library. -{-# DEPRECATED next "No longer used" #-} -{-# DEPRECATED genRange "No longer used" #-} -class RandomGen g where - {-# MINIMAL split,(genWord32|genWord64|(next,genRange)) #-} - -- | Returns an 'Int' that is uniformly distributed over the range returned by - -- 'genRange' (including both end points), and a new generator. Using 'next' - -- is inefficient as all operations go via 'Integer'. See - -- [here](https://alexey.kuleshevi.ch/blog/2019/12/21/random-benchmarks) for - -- more details. It is thus deprecated. - next :: g -> (Int, g) - next g = runGenState g (uniformRM (genRange g)) - - -- | Returns a 'Word8' that is uniformly distributed over the entire 'Word8' - -- range. - -- - -- @since 1.2 - genWord8 :: g -> (Word8, g) - genWord8 = first fromIntegral . genWord32 - - -- | Returns a 'Word16' that is uniformly distributed over the entire 'Word16' - -- range. - -- - -- @since 1.2 - genWord16 :: g -> (Word16, g) - genWord16 = first fromIntegral . genWord32 - - -- | Returns a 'Word32' that is uniformly distributed over the entire 'Word32' - -- range. - -- - -- @since 1.2 - genWord32 :: g -> (Word32, g) - genWord32 = randomIvalIntegral (minBound, maxBound) - -- Once `next` is removed, this implementation should be used instead: - -- first fromIntegral . genWord64 - - -- | Returns a 'Word64' that is uniformly distributed over the entire 'Word64' - -- range. - -- - -- @since 1.2 - genWord64 :: g -> (Word64, g) - genWord64 g = - case genWord32 g of - (l32, g') -> - case genWord32 g' of - (h32, g'') -> - ((fromIntegral h32 `unsafeShiftL` 32) .|. fromIntegral l32, g'') - - -- | @genWord32R upperBound g@ returns a 'Word32' that is uniformly - -- distributed over the range @[0, upperBound]@. - -- - -- @since 1.2 - genWord32R :: Word32 -> g -> (Word32, g) - genWord32R m g = runGenState g (unbiasedWordMult32 m) - - -- | @genWord64R upperBound g@ returns a 'Word64' that is uniformly - -- distributed over the range @[0, upperBound]@. - -- - -- @since 1.2 - genWord64R :: Word64 -> g -> (Word64, g) - genWord64R m g = runGenState g (unsignedBitmaskWithRejectionM uniformWord64 m) - - -- | @genShortByteString n g@ returns a 'ShortByteString' of length @n@ - -- filled with pseudo-random bytes. - -- - -- @since 1.2 - genShortByteString :: Int -> g -> (ShortByteString, g) - genShortByteString n g = - unsafePerformIO $ runGenStateT g (genShortByteStringIO n . uniformWord64) - {-# INLINE genShortByteString #-} - - -- | Yields the range of values returned by 'next'. - -- - -- It is required that: - -- - -- * If @(a, b) = 'genRange' g@, then @a < b@. - -- * 'genRange' must not examine its argument so the value it returns is - -- determined only by the instance of 'RandomGen'. - -- - -- The default definition spans the full range of 'Int'. - genRange :: g -> (Int, Int) - genRange _ = (minBound, maxBound) - - -- | Returns two distinct pseudo-random number generators. - -- - -- Implementations should take care to ensure that the resulting generators - -- are not correlated. Some pseudo-random number generators are not - -- splittable. In that case, the 'split' implementation should fail with a - -- descriptive 'error' message. - split :: g -> (g, g) - --- | 'MonadRandom' is an interface to monadic pseudo-random number generators. -class Monad m => MonadRandom g s m | g m -> s where - -- | Represents the state of the pseudo-random number generator for use with - -- 'thawGen' and 'freezeGen'. - -- - -- @since 1.2 - data Frozen g :: * - {-# MINIMAL freezeGen,thawGen,(uniformWord32|uniformWord64) #-} - - -- | Restores the pseudo-random number generator from its 'Frozen' - -- representation. - -- - -- @since 1.2 - thawGen :: Frozen g -> m (g s) - - -- | Saves the state of the pseudo-random number generator to its 'Frozen' - -- representation. - -- - -- @since 1.2 - freezeGen :: g s -> m (Frozen g) - - -- | @uniformWord32R upperBound g@ generates a 'Word32' that is uniformly - -- distributed over the range @[0, upperBound]@. - -- - -- @since 1.2 - uniformWord32R :: Word32 -> g s -> m Word32 - uniformWord32R = unsignedBitmaskWithRejectionM uniformWord32 - - -- | @uniformWord64R upperBound g@ generates a 'Word64' that is uniformly - -- distributed over the range @[0, upperBound]@. - -- - -- @since 1.2 - uniformWord64R :: Word64 -> g s -> m Word64 - uniformWord64R = unsignedBitmaskWithRejectionM uniformWord64 - - -- | Generates a 'Word8' that is uniformly distributed over the entire 'Word8' - -- range. - -- - -- The default implementation extracts a 'Word8' from 'uniformWord32'. - -- - -- @since 1.2 - uniformWord8 :: g s -> m Word8 - uniformWord8 = fmap fromIntegral . uniformWord32 - - -- | Generates a 'Word16' that is uniformly distributed over the entire - -- 'Word16' range. - -- - -- The default implementation extracts a 'Word16' from 'uniformWord32'. - -- - -- @since 1.2 - uniformWord16 :: g s -> m Word16 - uniformWord16 = fmap fromIntegral . uniformWord32 - - -- | Generates a 'Word32' that is uniformly distributed over the entire - -- 'Word32' range. - -- - -- The default implementation extracts a 'Word32' from 'uniformWord64'. - -- - -- @since 1.2 - uniformWord32 :: g s -> m Word32 - uniformWord32 = fmap fromIntegral . uniformWord64 - - -- | Generates a 'Word64' that is uniformly distributed over the entire - -- 'Word64' range. - -- - -- The default implementation combines two 'Word32' from 'uniformWord32' into - -- one 'Word64'. - -- - -- @since 1.2 - uniformWord64 :: g s -> m Word64 - uniformWord64 g = do - l32 <- uniformWord32 g - h32 <- uniformWord32 g - pure (unsafeShiftL (fromIntegral h32) 32 .|. fromIntegral l32) - - -- | @uniformShortByteString n g@ generates a 'ShortByteString' of length @n@ - -- filled with pseudo-random bytes. - -- - -- @since 1.2 - uniformShortByteString :: Int -> g s -> m ShortByteString - default uniformShortByteString :: MonadIO m => Int -> g s -> m ShortByteString - uniformShortByteString n = genShortByteStringIO n . uniformWord64 - {-# INLINE uniformShortByteString #-} - -------------------------------------------------------------------------------- --- Monadic adapters -------------------------------------------------------------------------------- - --- $monadicadapters --- --- Pure pseudo-random number generators can be used in monadic code via the --- adapters 'PureGen', 'AtomicGen', 'IOGen' and 'STGen'. --- --- * 'PureGen' can be used in any state monad. With strict 'StateT' there is --- no performance overhead compared to using the 'RandomGen' instance --- directly. 'PureGen' is /not/ safe to use in the presence of exceptions --- and concurrency. --- --- * 'AtomicGen' is safe in the presence of exceptions and concurrency since --- it performs all actions atomically. +-- ['System.Random.Monad.MonadRandom': monadic pseudo-random number generators] +-- See "System.Random.Monad" module -- --- * 'IOGen' is a wrapper around an 'IORef' that holds a pure generator. --- 'IOGen' is safe in the presence of exceptions, but not concurrency. --- --- * 'STGen' is a wrapper around an 'STRef' that holds a pure generator. --- 'STGen' is safe in the presence of exceptions, but not concurrency. - --- | Interface to operations on 'RandomGen' wrappers like 'IOGen' and 'PureGen'. --- --- @since 1.2 -class (RandomGen r, MonadRandom (g r) s m) => RandomGenM g r s m where - applyRandomGenM :: (r -> (a, r)) -> g r s -> m a - --- | Splits a pseudo-random number generator into two. Overwrites the mutable --- wrapper with one of the resulting generators and returns the other. --- --- @since 1.2 -splitRandomGenM :: RandomGenM g r s m => g r s -> m r -splitRandomGenM = applyRandomGenM split - -instance (RandomGen r, MonadIO m) => RandomGenM IOGen r RealWorld m where - applyRandomGenM = applyIOGen - -instance (RandomGen r, MonadIO m) => RandomGenM AtomicGen r RealWorld m where - applyRandomGenM = applyAtomicGen - -instance (RandomGen r, MonadState r m) => RandomGenM PureGen r r m where - applyRandomGenM f _ = state f - -instance RandomGen r => RandomGenM STGen r s (ST s) where - applyRandomGenM = applySTGen - --- | Runs a mutable pseudo-random number generator from its 'Frozen' state. --- --- >>> import Data.Int (Int8) --- >>> runGenM (IOGen (mkStdGen 217)) (`uniformListM` 5) :: IO ([Int8], Frozen (IOGen StdGen)) --- ([-74,37,-50,-2,3],IOGen {unIOGen = SMGen 4273268533320920145 15251669095119325999}) --- --- @since 1.2 -runGenM :: MonadRandom g s m => Frozen g -> (g s -> m a) -> m (a, Frozen g) -runGenM fg action = do - g <- thawGen fg - res <- action g - fg' <- freezeGen g - pure (res, fg') - --- | Same as 'runGenM', but only returns the generated value. --- --- @since 1.2 -runGenM_ :: MonadRandom g s m => Frozen g -> (g s -> m a) -> m a -runGenM_ fg action = fst <$> runGenM fg action - --- | Generates a list of pseudo-random values. --- --- @since 1.2 -uniformListM :: (MonadRandom g s m, Uniform a) => g s -> Int -> m [a] -uniformListM gen n = replicateM n (uniformM gen) - -data MBA s = MBA (MutableByteArray# s) - - --- | Efficiently generates a sequence of pseudo-random bytes in a platform --- independent manner. The allocated memory is be pinned, so it is safe to use --- with FFI calls. --- --- @since 1.2 -genShortByteStringIO :: MonadIO m => Int -> m Word64 -> m ShortByteString -genShortByteStringIO n0 gen64 = do - let !n@(I# n#) = max 0 n0 - (n64, nrem64) = n `quotRem` 8 - MBA mba# <- - liftIO $ - IO $ \s# -> - case newPinnedByteArray# n# s# of - (# s'#, mba# #) -> (# s'#, MBA mba# #) - let go i ptr - | i < n64 = do - w64 <- gen64 - -- Writing 8 bytes at a time in a Little-endian order gives us - -- platform portability - liftIO $ runF word64LE w64 ptr - go (i + 1) (ptr `plusPtr` 8) - | otherwise = return ptr - ptr <- go 0 (Ptr (byteArrayContents# (unsafeCoerce# mba#))) - when (nrem64 > 0) $ do - w64 <- gen64 - -- In order to not mess up the byte order we write generated Word64 into a - -- temporary pointer and then copy only the missing bytes over to the array. - -- It is tempting to simply generate as many bytes as we still need using - -- smaller generators (eg. uniformWord8), but that would result in - -- inconsistent tail when total length is slightly varied. - liftIO $ - alloca $ \w64ptr -> do - runF word64LE w64 w64ptr - forM_ [0 .. nrem64 - 1] $ \i -> do - w8 :: Word8 <- peekByteOff w64ptr i - pokeByteOff ptr i w8 - liftIO $ - IO $ \s# -> - case unsafeFreezeByteArray# mba# s# of - (# s'#, ba# #) -> (# s'#, SBS ba# #) -{-# INLINE genShortByteStringIO #-} - --- | Same as 'genShortByteStringIO', but runs in 'ST'. --- --- @since 1.2 -genShortByteStringST :: Int -> ST s Word64 -> ST s ShortByteString -genShortByteStringST n action = - unsafeIOToST (genShortByteStringIO n (unsafeSTToIO action)) - -pinnedByteArrayToByteString :: ByteArray# -> ByteString -pinnedByteArrayToByteString ba# = - PS (pinnedByteArrayToForeignPtr ba#) 0 (I# (sizeofByteArray# ba#)) -{-# INLINE pinnedByteArrayToByteString #-} - -pinnedByteArrayToForeignPtr :: ByteArray# -> ForeignPtr a -pinnedByteArrayToForeignPtr ba# = - ForeignPtr (byteArrayContents# ba#) (PlainPtr (unsafeCoerce# ba#)) -{-# INLINE pinnedByteArrayToForeignPtr #-} - --- | Generates a pseudo-random 'ByteString' of the specified size. --- --- @since 1.2 -uniformByteString :: MonadRandom g s m => Int -> g s -> m ByteString -uniformByteString n g = do - ba@(SBS ba#) <- uniformShortByteString n g - pure $ - if isTrue# (isByteArrayPinned# ba#) - then pinnedByteArrayToByteString ba# - else fromShort ba -{-# INLINE uniformByteString #-} - --- | Generates a 'ByteString' of the specified size using a pure pseudo-random --- number generator. See 'uniformByteString' for the monadic version. --- --- @since 1.2 -genByteString :: RandomGen g => Int -> g -> (ByteString, g) -genByteString n g = runPureGenST g (uniformByteString n) -{-# INLINE genByteString #-} - --- | Runs a monadic generating action in the `ST` monad using a pure --- pseudo-random number generator. --- --- @since 1.2 -runPureGenST :: RandomGen g => g -> (forall s . PureGen g g -> StateT g (ST s) a) -> (a, g) -runPureGenST g action = runST $ runGenStateT g $ action -{-# INLINE runPureGenST #-} - - --- | Opaque data type that carries the type of a pure pseudo-random number --- generator. --- --- @since 1.2 -data PureGen g s = PureGenI - -instance (RandomGen g, MonadState g m) => MonadRandom (PureGen g) g m where - newtype Frozen (PureGen g) = PureGen g - thawGen (PureGen g) = PureGenI <$ put g - freezeGen _ = fmap PureGen get - uniformWord32R r _ = state (genWord32R r) - uniformWord64R r _ = state (genWord64R r) - uniformWord8 _ = state genWord8 - uniformWord16 _ = state genWord16 - uniformWord32 _ = state genWord32 - uniformWord64 _ = state genWord64 - uniformShortByteString n _ = state (genShortByteString n) -- | Pure version of `uniformM` that works with instances of `RandomGen` -- @@ -605,258 +109,14 @@ uniform g = runGenState g uniformM uniformR :: (RandomGen g, UniformRange a) => g -> (a, a) -> (a, g) uniformR g r = runGenState g (uniformRM r) --- | Generates a pseudo-random value in a state monad. --- --- @since 1.2 -genRandom :: (RandomGen g, Random a, MonadState g m) => PureGen g g -> m a -genRandom _ = state random - --- | Splits a pseudo-random number generator into two. Updates the state with --- one of the resulting generators and returns the other. --- --- @since 1.2 -splitGen :: (MonadState g m, RandomGen g) => m g -splitGen = state split - --- | Runs a monadic generating action in the `State` monad using a pure --- pseudo-random number generator. --- --- @since 1.2 -runGenState :: RandomGen g => g -> (PureGen g g -> State g a) -> (a, g) -runGenState g f = runState (f PureGenI) g - --- | Runs a monadic generating action in the `State` monad using a pure --- pseudo-random number generator. Returns only the resulting pseudo-random --- value. --- --- @since 1.2 -runGenState_ :: RandomGen g => g -> (PureGen g g -> State g a) -> a -runGenState_ g = fst . runGenState g - --- | Runs a monadic generating action in the `StateT` monad using a pure --- pseudo-random number generator. --- --- @since 1.2 -runGenStateT :: RandomGen g => g -> (PureGen g g -> StateT g m a) -> m (a, g) -runGenStateT g f = runStateT (f PureGenI) g - --- | Runs a monadic generating action in the `StateT` monad using a pure --- pseudo-random number generator. Returns only the resulting pseudo-random --- value. --- --- @since 1.2 -runGenStateT_ :: (RandomGen g, Functor f) => g -> (PureGen g g -> StateT g f a) -> f a -runGenStateT_ g = fmap fst . runGenStateT g - --- | This is a wrapper around pure generator that can be used in a monadic --- environment. It is safe in presence of exceptions and concurrency since all --- operations are performed atomically. --- --- @since 1.2 -newtype AtomicGen g s = AtomicGenI (IORef g) - -instance (RandomGen g, MonadIO m) => MonadRandom (AtomicGen g) RealWorld m where - newtype Frozen (AtomicGen g) = AtomicGen { unAtomicGen :: g } - deriving (Eq, Show, Read) - thawGen (AtomicGen g) = fmap AtomicGenI (liftIO $ newIORef g) - freezeGen (AtomicGenI gVar) = fmap AtomicGen (liftIO $ readIORef gVar) - uniformWord32R r = applyAtomicGen (genWord32R r) - {-# INLINE uniformWord32R #-} - uniformWord64R r = applyAtomicGen (genWord64R r) - {-# INLINE uniformWord64R #-} - uniformWord8 = applyAtomicGen genWord8 - {-# INLINE uniformWord8 #-} - uniformWord16 = applyAtomicGen genWord16 - {-# INLINE uniformWord16 #-} - uniformWord32 = applyAtomicGen genWord32 - {-# INLINE uniformWord32 #-} - uniformWord64 = applyAtomicGen genWord64 - {-# INLINE uniformWord64 #-} - uniformShortByteString n = applyAtomicGen (genShortByteString n) - --- | Atomically applies a pure operation to the wrapped pseudo-random number --- generator. --- --- @since 1.2 -applyAtomicGen :: MonadIO m => (g -> (a, g)) -> AtomicGen g RealWorld -> m a -applyAtomicGen op (AtomicGenI gVar) = - liftIO $ atomicModifyIORef' gVar $ \g -> - case op g of - (a, g') -> (g', a) -{-# INLINE applyAtomicGen #-} - --- | This is a wrapper around an @IORef@ that holds a pure generator. Because of --- extra pointer indirection it will be slightly slower than if `PureGen` is --- being used, but faster than `AtomicGen` wrapper, since atomic modification is --- not being used with `IOGen`. Which also means that it is not safe in a --- concurrent setting. --- --- Both `IOGen` and `AtomicGen` are necessary when generation of pseudo-random --- values happens in `IO` and especially when dealing with exception handling --- and resource allocation, which is where `StateT` should never be used. For --- example writing a pseudo-random number of bytes into a temporary file: --- --- >>> import UnliftIO.Temporary (withSystemTempFile) --- >>> import Data.ByteString (hPutStr) --- >>> let ioGen g = withSystemTempFile "foo.bin" $ \_ h -> uniformRM (0, 100) g >>= flip uniformByteString g >>= hPutStr h --- --- and then run it: --- --- >>> runGenM_ (IOGen (mkStdGen 1729)) ioGen --- --- @since 1.2 -newtype IOGen g s = IOGenI (IORef g) - -instance (RandomGen g, MonadIO m) => MonadRandom (IOGen g) RealWorld m where - newtype Frozen (IOGen g) = IOGen { unIOGen :: g } - deriving (Eq, Show, Read) - thawGen (IOGen g) = fmap IOGenI (liftIO $ newIORef g) - freezeGen (IOGenI gVar) = fmap IOGen (liftIO $ readIORef gVar) - uniformWord32R r = applyIOGen (genWord32R r) - {-# INLINE uniformWord32R #-} - uniformWord64R r = applyIOGen (genWord64R r) - {-# INLINE uniformWord64R #-} - uniformWord8 = applyIOGen genWord8 - {-# INLINE uniformWord8 #-} - uniformWord16 = applyIOGen genWord16 - {-# INLINE uniformWord16 #-} - uniformWord32 = applyIOGen genWord32 - {-# INLINE uniformWord32 #-} - uniformWord64 = applyIOGen genWord64 - {-# INLINE uniformWord64 #-} - uniformShortByteString n = applyIOGen (genShortByteString n) - --- | Applies a pure operation to the wrapped pseudo-random number generator. --- --- @since 1.2 -applyIOGen :: MonadIO m => (g -> (a, g)) -> IOGen g RealWorld -> m a -applyIOGen f (IOGenI ref) = liftIO $ do - g <- readIORef ref - case f g of - (!a, !g') -> a <$ writeIORef ref g' -{-# INLINE applyIOGen #-} - - --- | This is a wrapper wround an @STRef@ that holds a pure generator. Because of --- extra pointer indirection it will be slightly slower than if `PureGen` is --- being used. --- --- @since 1.2 -newtype STGen g s = STGenI (STRef s g) - -instance RandomGen g => MonadRandom (STGen g) s (ST s) where - newtype Frozen (STGen g) = STGen { unSTGen :: g } - deriving (Eq, Show, Read) - thawGen (STGen g) = fmap STGenI (newSTRef g) - freezeGen (STGenI gVar) = fmap STGen (readSTRef gVar) - uniformWord32R r = applySTGen (genWord32R r) - {-# INLINE uniformWord32R #-} - uniformWord64R r = applySTGen (genWord64R r) - {-# INLINE uniformWord64R #-} - uniformWord8 = applySTGen genWord8 - {-# INLINE uniformWord8 #-} - uniformWord16 = applySTGen genWord16 - {-# INLINE uniformWord16 #-} - uniformWord32 = applySTGen genWord32 - {-# INLINE uniformWord32 #-} - uniformWord64 = applySTGen genWord64 - {-# INLINE uniformWord64 #-} - uniformShortByteString n = applySTGen (genShortByteString n) - --- | Applies a pure operation to the wrapped pseudo-random number generator. --- --- @since 1.2 -applySTGen :: (g -> (a, g)) -> STGen g s -> ST s a -applySTGen f (STGenI ref) = do - g <- readSTRef ref - case f g of - (!a, !g') -> a <$ writeSTRef ref g' -{-# INLINE applySTGen #-} - --- | Runs a monadic generating action in the `ST` monad using a pure --- pseudo-random number generator. --- --- @since 1.2 -runSTGen :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> (a, g) -runSTGen g action = unSTGen <$> runST (runGenM (STGen g) action) - --- | Runs a monadic generating action in the `ST` monad using a pure --- pseudo-random number generator. Returns only the resulting pseudo-random --- value. +-- | Generates a 'ByteString' of the specified size using a pure pseudo-random +-- number generator. See 'uniformByteString' for the monadic version. -- -- @since 1.2 -runSTGen_ :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> a -runSTGen_ g action = fst $ runSTGen g action - --- | The standard pseudo-random number generator. -type StdGen = SM.SMGen - -instance RandomGen StdGen where - next = SM.nextInt - genWord32 = SM.nextWord32 - genWord64 = SM.nextWord64 - split = SM.splitSMGen - -instance RandomGen SM32.SMGen where - next = SM32.nextInt - genWord32 = SM32.nextWord32 - genWord64 = SM32.nextWord64 - split = SM32.splitSMGen - --- | Constructs a 'StdGen' deterministically. -mkStdGen :: Int -> StdGen -mkStdGen s = SM.mkSMGen $ fromIntegral s - - --- $uniform --- This library provides two type classes to generate pseudo-random values: --- --- * 'UniformRange' is used to generate a value of a datatype uniformly --- within an inclusive range. --- * 'Uniform' is used to generate a value of a datatype uniformly over all --- possible values of that datatype. --- --- Types may have instances for both or just one of 'UniformRange' and --- 'Uniform'. A few examples illustrate this: --- --- * 'Int', 'Word16' and 'Bool' are instances of both 'UniformRange' and --- 'Uniform'. --- * 'Integer', 'Float' and 'Double' each have an instance for 'UniformRange' --- but no 'Uniform' instance. --- * A hypothetical type @Radian@ representing angles by taking values in the --- range @[0, 2π)@ has a trivial 'Uniform' instance, but no 'UniformRange' --- instance: the problem is that two given @Radian@ values always span /two/ --- ranges, one clockwise and one anti-clockwise. --- * It is trivial to construct a @Uniform (a, b)@ instance given --- @Uniform a@ and @Uniform b@ (and this library provides this tuple --- instance). --- * On the other hand, there is no correct way to construct a --- @UniformRange (a, b)@ instance based on just @UniformRange a@ and --- @UniformRange b@. - +genByteString :: RandomGen g => Int -> g -> (ByteString, g) +genByteString n g = runPureGenST g (uniformByteString n) +{-# INLINE genByteString #-} --- | Generates a value uniformly distributed over all possible values of that --- datatype. --- --- @since 1.2 -class Uniform a where - uniformM :: MonadRandom g s m => g s -> m a - --- | Generates a value uniformly distributed over the provided inclusive range. --- --- For example, @uniformR (1,4)@ should generate values uniformly from the set --- @[1,2,3,4]@. --- --- The API uses an inclusive range so any range can be expressed, even when --- using fixed-size ints, enumerations etc. --- --- The following law should hold to make the function always defined: --- --- > uniformRM (a,b) = uniformM (b,a) --- --- @since 1.2 -class UniformRange a where - uniformRM :: MonadRandom g s m => (a, a) -> g s -> m a {- | With a source of pseudo-random number supply in hand, the 'Random' class allows @@ -931,504 +191,51 @@ buildRandoms cons rand = go -- Generate values in the Int range instance Random Integer where random = first (toInteger :: Int -> Integer) . random - -instance UniformRange Integer where - uniformRM = uniformIntegerM - instance Random Int8 -instance Uniform Int8 where - uniformM = fmap (fromIntegral :: Word8 -> Int8) . uniformWord8 -instance UniformRange Int8 where - uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int8 -> Word8) fromIntegral - instance Random Int16 -instance Uniform Int16 where - uniformM = fmap (fromIntegral :: Word16 -> Int16) . uniformWord16 -instance UniformRange Int16 where - uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int16 -> Word16) fromIntegral - instance Random Int32 -instance Uniform Int32 where - uniformM = fmap (fromIntegral :: Word32 -> Int32) . uniformWord32 -instance UniformRange Int32 where - uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int32 -> Word32) fromIntegral - instance Random Int64 -instance Uniform Int64 where - uniformM = fmap (fromIntegral :: Word64 -> Int64) . uniformWord64 -instance UniformRange Int64 where - uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int64 -> Word64) fromIntegral - instance Random Int -instance Uniform Int where -#if WORD_SIZE_IN_BITS < 64 - uniformM = fmap (fromIntegral :: Word32 -> Int) . uniformWord32 -#else - uniformM = fmap (fromIntegral :: Word64 -> Int) . uniformWord64 -#endif - {-# INLINE uniformM #-} -instance UniformRange Int where - uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int -> Word) fromIntegral - {-# INLINE uniformRM #-} - instance Random Word -instance Uniform Word where -#if WORD_SIZE_IN_BITS < 64 - uniformM = fmap (fromIntegral :: Word32 -> Word) . uniformWord32 -#else - uniformM = fmap (fromIntegral :: Word64 -> Word) . uniformWord64 -#endif -instance UniformRange Word where - {-# INLINE uniformRM #-} - uniformRM = unsignedBitmaskWithRejectionRM - instance Random Word8 -instance Uniform Word8 where - {-# INLINE uniformM #-} - uniformM = uniformWord8 -instance UniformRange Word8 where - {-# INLINE uniformRM #-} - uniformRM = unsignedBitmaskWithRejectionRM - instance Random Word16 -instance Uniform Word16 where - {-# INLINE uniformM #-} - uniformM = uniformWord16 -instance UniformRange Word16 where - {-# INLINE uniformRM #-} - uniformRM = unsignedBitmaskWithRejectionRM - instance Random Word32 -instance Uniform Word32 where - {-# INLINE uniformM #-} - uniformM = uniformWord32 -instance UniformRange Word32 where - {-# INLINE uniformRM #-} - uniformRM (b, t) g | b > t = (+t) <$> unbiasedWordMult32 (b - t) g - | otherwise = (+b) <$> unbiasedWordMult32 (t - b) g - instance Random Word64 -instance Uniform Word64 where - {-# INLINE uniformM #-} - uniformM = uniformWord64 -instance UniformRange Word64 where - {-# INLINE uniformRM #-} - uniformRM = unsignedBitmaskWithRejectionRM - instance Random CBool -instance Uniform CBool where - uniformM = fmap CBool . uniformM -instance UniformRange CBool where - uniformRM (CBool b, CBool t) = fmap CBool . uniformRM (b, t) - instance Random CChar -instance Uniform CChar where - uniformM = fmap CChar . uniformM -instance UniformRange CChar where - uniformRM (CChar b, CChar t) = fmap CChar . uniformRM (b, t) - instance Random CSChar -instance Uniform CSChar where - uniformM = fmap CSChar . uniformM -instance UniformRange CSChar where - uniformRM (CSChar b, CSChar t) = fmap CSChar . uniformRM (b, t) - instance Random CUChar -instance Uniform CUChar where - uniformM = fmap CUChar . uniformM -instance UniformRange CUChar where - uniformRM (CUChar b, CUChar t) = fmap CUChar . uniformRM (b, t) - instance Random CShort -instance Uniform CShort where - uniformM = fmap CShort . uniformM -instance UniformRange CShort where - uniformRM (CShort b, CShort t) = fmap CShort . uniformRM (b, t) - instance Random CUShort -instance Uniform CUShort where - uniformM = fmap CUShort . uniformM -instance UniformRange CUShort where - uniformRM (CUShort b, CUShort t) = fmap CUShort . uniformRM (b, t) - instance Random CInt -instance Uniform CInt where - uniformM = fmap CInt . uniformM -instance UniformRange CInt where - uniformRM (CInt b, CInt t) = fmap CInt . uniformRM (b, t) - instance Random CUInt -instance Uniform CUInt where - uniformM = fmap CUInt . uniformM -instance UniformRange CUInt where - uniformRM (CUInt b, CUInt t) = fmap CUInt . uniformRM (b, t) - instance Random CLong -instance Uniform CLong where - uniformM = fmap CLong . uniformM -instance UniformRange CLong where - uniformRM (CLong b, CLong t) = fmap CLong . uniformRM (b, t) - instance Random CULong -instance Uniform CULong where - uniformM = fmap CULong . uniformM -instance UniformRange CULong where - uniformRM (CULong b, CULong t) = fmap CULong . uniformRM (b, t) - instance Random CPtrdiff -instance Uniform CPtrdiff where - uniformM = fmap CPtrdiff . uniformM -instance UniformRange CPtrdiff where - uniformRM (CPtrdiff b, CPtrdiff t) = fmap CPtrdiff . uniformRM (b, t) - instance Random CSize -instance Uniform CSize where - uniformM = fmap CSize . uniformM -instance UniformRange CSize where - uniformRM (CSize b, CSize t) = fmap CSize . uniformRM (b, t) - instance Random CWchar -instance Uniform CWchar where - uniformM = fmap CWchar . uniformM -instance UniformRange CWchar where - uniformRM (CWchar b, CWchar t) = fmap CWchar . uniformRM (b, t) - instance Random CSigAtomic -instance Uniform CSigAtomic where - uniformM = fmap CSigAtomic . uniformM -instance UniformRange CSigAtomic where - uniformRM (CSigAtomic b, CSigAtomic t) = fmap CSigAtomic . uniformRM (b, t) - instance Random CLLong -instance Uniform CLLong where - uniformM = fmap CLLong . uniformM -instance UniformRange CLLong where - uniformRM (CLLong b, CLLong t) = fmap CLLong . uniformRM (b, t) - instance Random CULLong -instance Uniform CULLong where - uniformM = fmap CULLong . uniformM -instance UniformRange CULLong where - uniformRM (CULLong b, CULLong t) = fmap CULLong . uniformRM (b, t) - instance Random CIntPtr -instance Uniform CIntPtr where - uniformM = fmap CIntPtr . uniformM -instance UniformRange CIntPtr where - uniformRM (CIntPtr b, CIntPtr t) = fmap CIntPtr . uniformRM (b, t) - instance Random CUIntPtr -instance Uniform CUIntPtr where - uniformM = fmap CUIntPtr . uniformM -instance UniformRange CUIntPtr where - uniformRM (CUIntPtr b, CUIntPtr t) = fmap CUIntPtr . uniformRM (b, t) - instance Random CIntMax -instance Uniform CIntMax where - uniformM = fmap CIntMax . uniformM -instance UniformRange CIntMax where - uniformRM (CIntMax b, CIntMax t) = fmap CIntMax . uniformRM (b, t) - instance Random CUIntMax -instance Uniform CUIntMax where - uniformM = fmap CUIntMax . uniformM -instance UniformRange CUIntMax where - uniformRM (CUIntMax b, CUIntMax t) = fmap CUIntMax . uniformRM (b, t) - instance Random CFloat where randomR (CFloat l, CFloat h) = first CFloat . randomR (l, h) random = first CFloat . random -instance UniformRange CFloat where - uniformRM (CFloat l, CFloat h) = fmap CFloat . uniformRM (l, h) - instance Random CDouble where randomR (CDouble l, CDouble h) = first CDouble . randomR (l, h) random = first CDouble . random -instance UniformRange CDouble where - uniformRM (CDouble l, CDouble h) = fmap CDouble . uniformRM (l, h) - - --- The `chr#` and `ord#` are the prim functions that will be called, regardless of which --- way you gonna do the `Char` conversion, so it is better to call them directly and --- bypass all the hoops. Also because `intToChar` and `charToInt` are internal functions --- and are called on valid character ranges it is impossible to generate an invalid --- `Char`, therefore it is totally fine to omit all the unnecessary checks involved in --- other paths of conversion. -word32ToChar :: Word32 -> Char -word32ToChar (W32# w#) = C# (chr# (word2Int# w#)) -{-# INLINE word32ToChar #-} - -charToWord32 :: Char -> Word32 -charToWord32 (C# c#) = W32# (int2Word# (ord# c#)) -{-# INLINE charToWord32 #-} instance Random Char -instance Uniform Char where - uniformM g = word32ToChar <$> unsignedBitmaskWithRejectionM uniformM (charToWord32 maxBound) g - {-# INLINE uniformM #-} -instance UniformRange Char where - uniformRM (l, h) g = - word32ToChar <$> unsignedBitmaskWithRejectionRM (charToWord32 l, charToWord32 h) g - {-# INLINE uniformRM #-} - instance Random Bool -instance Uniform Bool where - uniformM = fmap wordToBool . uniformWord8 - where wordToBool w = (w .&. 1) /= 0 -instance UniformRange Bool where - uniformRM (False, False) _g = return False - uniformRM (True, True) _g = return True - uniformRM _ g = uniformM g - instance Random Double where randomR r g = runGenState g (uniformRM r) random g = runGenState g (uniformRM (0, 1)) - -instance UniformRange Double where - uniformRM (l, h) g = do - w64 <- uniformWord64 g - let x = word64ToDoubleInUnitInterval w64 - return $ (h - l) * x + l - --- | Turns a given uniformly distributed 'Word64' value into a uniformly --- distributed 'Double' value in the range [0, 1). -word64ToDoubleInUnitInterval :: Word64 -> Double -word64ToDoubleInUnitInterval w64 = between1and2 - 1.0 - where - between1and2 = castWord64ToDouble $ (w64 `unsafeShiftR` 12) .|. 0x3ff0000000000000 -{-# INLINE word64ToDoubleInUnitInterval #-} - --- | These are now in 'GHC.Float' but unpatched in some versions so --- for now we roll our own. See --- https://gitlab.haskell.org/ghc/ghc/-/blob/6d172e63f3dd3590b0a57371efb8f924f1fcdf05/libraries/base/GHC/Float.hs -{-# INLINE castWord32ToFloat #-} -castWord32ToFloat :: Word32 -> Float -castWord32ToFloat (W32# w#) = F# (stgWord32ToFloat w#) - -foreign import prim "stg_word32ToFloatyg" - stgWord32ToFloat :: Word# -> Float# - -{-# INLINE castWord64ToDouble #-} -castWord64ToDouble :: Word64 -> Double -castWord64ToDouble (W64# w) = D# (stgWord64ToDouble w) - -foreign import prim "stg_word64ToDoubleyg" -#if WORD_SIZE_IN_BITS == 64 - stgWord64ToDouble :: Word# -> Double# -#else - stgWord64ToDouble :: Word64# -> Double# -#endif - - instance Random Float where randomR r g = runGenState g (uniformRM r) random g = runGenState g (uniformRM (0, 1)) -instance UniformRange Float where - uniformRM (l, h) g = do - w32 <- uniformWord32 g - let x = word32ToFloatInUnitInterval w32 - return $ (h - l) * x + l - --- | Turns a given uniformly distributed 'Word32' value into a uniformly --- distributed 'Float' value in the range [0,1). -word32ToFloatInUnitInterval :: Word32 -> Float -word32ToFloatInUnitInterval w32 = between1and2 - 1.0 - where - between1and2 = castWord32ToFloat $ (w32 `unsafeShiftR` 9) .|. 0x3f800000 -{-# INLINE word32ToFloatInUnitInterval #-} - --- The two integer functions below take an [inclusive,inclusive] range. -randomIvalIntegral :: (RandomGen g, Integral a) => (a, a) -> g -> (a, g) -randomIvalIntegral (l,h) = randomIvalInteger (toInteger l, toInteger h) - -{-# SPECIALIZE randomIvalInteger :: (Num a) => - (Integer, Integer) -> StdGen -> (a, StdGen) #-} - -randomIvalInteger :: (RandomGen g, Num a) => (Integer, Integer) -> g -> (a, g) -randomIvalInteger (l,h) rng - | l > h = randomIvalInteger (h,l) rng - | otherwise = case (f 1 0 rng) of (v, rng') -> (fromInteger (l + v `mod` k), rng') - where - (genlo, genhi) = genRange rng - b = fromIntegral genhi - fromIntegral genlo + 1 - - -- Probabilities of the most likely and least likely result - -- will differ at most by a factor of (1 +- 1/q). Assuming the RandomGen - -- is uniform, of course - - -- On average, log q / log b more pseudo-random values will be generated - -- than the minimum - q = 1000 - k = h - l + 1 - magtgt = k * q - - -- generate pseudo-random values until we exceed the target magnitude - f mag v g | mag >= magtgt = (v, g) - | otherwise = v' `seq`f (mag*b) v' g' where - (x,g') = next g - v' = (v * b + (fromIntegral x - fromIntegral genlo)) - --- | Generate an 'Integer' in the range @[l, h]@ if @l <= h@ and @[h, l]@ --- otherwise. -uniformIntegerM :: (MonadRandom g s m) => (Integer, Integer) -> g s -> m Integer -uniformIntegerM (l, h) gen = case l `compare` h of - LT -> do - let limit = h - l - let limitAsWord64 :: Word64 = fromIntegral limit - bounded <- - if (toInteger limitAsWord64) == limit - -- Optimisation: if 'limit' fits into 'Word64', generate a bounded - -- 'Word64' and then convert to 'Integer' - then toInteger <$> unsignedBitmaskWithRejectionM uniformWord64 limitAsWord64 gen - else boundedExclusiveIntegerM (limit + 1) gen - return $ l + bounded - GT -> uniformIntegerM (h, l) gen - EQ -> pure l -{-# INLINE uniformIntegerM #-} - --- | Generate an 'Integer' in the range @[0, s)@ using a variant of Lemire's --- multiplication method. --- --- Daniel Lemire. 2019. Fast Random Integer Generation in an Interval. In ACM --- Transactions on Modeling and Computer Simulation --- https://doi.org/10.1145/3230636 --- --- PRECONDITION (unchecked): s > 0 -boundedExclusiveIntegerM :: (MonadRandom g s m) => Integer -> g s -> m Integer -boundedExclusiveIntegerM s gen = go - where - n = integerWordSize s - -- We renamed 'L' from the paper to 'k' here because 'L' is not a valid - -- variable name in Haskell and 'l' is already used in the algorithm. - k = WORD_SIZE_IN_BITS * n - twoToK = (1::Integer) `shiftL` k - modTwoToKMask = twoToK - 1 - - t = (twoToK - s) `mod` s - go = do - x <- uniformIntegerWords n gen - let m = x * s - -- m .&. modTwoToKMask == m `mod` twoToK - let l = m .&. modTwoToKMask - if l < t - then go - -- m `shiftR` k == m `quot` twoToK - else return $ m `shiftR` k -{-# INLINE boundedExclusiveIntegerM #-} - --- | @integerWordSize i@ returns that least @w@ such that --- @i <= WORD_SIZE_IN_BITS^w@. -integerWordSize :: Integer -> Int -integerWordSize = go 0 - where - go !acc i - | i == 0 = acc - | otherwise = go (acc + 1) (i `shiftR` WORD_SIZE_IN_BITS) -{-# INLINE integerWordSize #-} - --- | @uniformIntegerWords n@ is a uniformly pseudo-random 'Integer' in the range --- @[0, WORD_SIZE_IN_BITS^n)@. -uniformIntegerWords :: (MonadRandom g s m) => Int -> g s -> m Integer -uniformIntegerWords n gen = go 0 n - where - go !acc i - | i == 0 = return acc - | otherwise = do - (w :: Word) <- uniformM gen - go ((acc `shiftL` WORD_SIZE_IN_BITS) .|. (fromIntegral w)) (i - 1) -{-# INLINE uniformIntegerWords #-} - --- | Uniformly generate Word32 in @[0, s]@. -unbiasedWordMult32 :: MonadRandom g s m => Word32 -> g s -> m Word32 -unbiasedWordMult32 s g - | s == maxBound = uniformWord32 g - | otherwise = unbiasedWordMult32Exclusive (s+1) g -{-# INLINE unbiasedWordMult32 #-} - --- | See [Lemire's paper](https://arxiv.org/pdf/1805.10941.pdf), --- [O\'Neill's --- blogpost](https://www.pcg-random.org/posts/bounded-rands.html) and --- more directly [O\'Neill's github --- repo](https://github.com/imneme/bounded-rands/blob/3d71f53c975b1e5b29f2f3b05a74e26dab9c3d84/bounded32.cpp#L234). --- N.B. The range is [0,t) **not** [0,t]. -unbiasedWordMult32Exclusive :: MonadRandom g s m => Word32 -> g s -> m Word32 -unbiasedWordMult32Exclusive r g = go - where - t :: Word32 - t = (-r) `mod` r -- Calculates 2^32 `mod` r!!! - go = do - x <- uniformWord32 g - let m :: Word64 - m = (fromIntegral x) * (fromIntegral r) - l :: Word32 - l = fromIntegral m - if (l >= t) then return (fromIntegral $ m `shiftR` 32) else go - --- | This only works for unsigned integrals -unsignedBitmaskWithRejectionRM :: - (MonadRandom g s m, FiniteBits a, Num a, Ord a, Uniform a) - => (a, a) - -> g s - -> m a -unsignedBitmaskWithRejectionRM (bottom, top) gen - | bottom > top = unsignedBitmaskWithRejectionRM (top, bottom) gen - | bottom == top = pure top - | otherwise = (bottom +) <$> unsignedBitmaskWithRejectionM uniformM range gen - where - range = top - bottom -{-# INLINE unsignedBitmaskWithRejectionRM #-} - --- | This works for signed integrals by explicit conversion to unsigned and abusing overflow -signedBitmaskWithRejectionRM :: - (Num a, Num b, Ord b, Ord a, FiniteBits a, MonadRandom g s f, Uniform a) - => (b -> a) - -> (a -> b) - -> (b, b) - -> g s - -> f b -signedBitmaskWithRejectionRM toUnsigned fromUnsigned (bottom, top) gen - | bottom > top = signedBitmaskWithRejectionRM toUnsigned fromUnsigned (top, bottom) gen - | bottom == top = pure top - | otherwise = (bottom +) . fromUnsigned <$> - unsignedBitmaskWithRejectionM uniformM range gen - where - -- This works in all cases, see Appendix 1 at the end of the file. - range = toUnsigned top - toUnsigned bottom -{-# INLINE signedBitmaskWithRejectionRM #-} - -unsignedBitmaskWithRejectionM :: (Ord a, FiniteBits a, Num a, MonadRandom g s m) => (g s -> m a) -> a -> g s -> m a -unsignedBitmaskWithRejectionM genUniformM range gen = go - where - mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) - go = do - x <- genUniformM gen - let x' = x .&. mask - if x' > range - then go - else pure x' -{-# INLINE unsignedBitmaskWithRejectionM #-} - -------------------------------------------------------------------------------- --- 'Uniform' instances for tuples -------------------------------------------------------------------------------- - -instance (Uniform a, Uniform b) => Uniform (a, b) where - uniformM g = (,) <$> uniformM g <*> uniformM g - -instance (Uniform a, Uniform b, Uniform c) => Uniform (a, b, c) where - uniformM g = (,,) <$> uniformM g <*> uniformM g <*> uniformM g - -instance (Uniform a, Uniform b, Uniform c, Uniform d) => Uniform (a, b, c, d) where - uniformM g = (,,,) <$> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g - -instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e) => Uniform (a, b, c, d, e) where - uniformM g = (,,,,) <$> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g - -instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e, Uniform f) => Uniform (a, b, c, d, e, f) where - uniformM g = (,,,,,) <$> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g - -instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e, Uniform f, Uniform g) => Uniform (a, b, c, d, e, f, g) where - uniformM g = (,,,,,,) <$> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g ------------------------------------------------------------------------------- -- Global pseudo-random number generator @@ -1522,33 +329,8 @@ getStdRandom f = atomicModifyIORef' theStdGen (swap . f) -- >>> :{ -- instance RandomGen PCGen where -- genWord32 = stepGen --- genWord64 g = (buildWord64 x y, g'') --- where --- (x, g') = stepGen g --- (y, g'') = stepGen g' -- :} -- --- This definition satisfies the compiler. However, the default implementations --- of 'genWord8' and 'genWord16' are geared towards backwards compatibility --- with 'RandomGen' instances based on 'next' and 'genRange'. This means that --- they are not optimal for pseudo-random number generators with a power-of-2 --- modulo. --- --- So let's implement a faster 'RandomGen' instance for our pseudo-random --- number generator as follows: --- --- >>> newtype PCGen' = PCGen' { unPCGen :: PCGen } --- >>> let stepGen' = second PCGen' . stepGen . unPCGen --- >>> :{ --- instance RandomGen PCGen' where --- genWord8 = first fromIntegral . stepGen' --- genWord16 = first fromIntegral . stepGen' --- genWord32 = stepGen' --- genWord64 g = (buildWord64 x y, g'') --- where --- (x, g') = stepGen' g --- (y, g'') = stepGen' g' --- :} -- -- === How to implement 'RandomGen' for a pseudo-random number generator without a power-of-2 modulus -- @@ -1613,8 +395,8 @@ getStdRandom f = atomicModifyIORef' theStdGen (swap . f) -- Version 1.2 mostly maintains backwards compatibility with version 1.1. This -- has a few consequences users should be aware of: -- --- * The type class 'Random' is deprecated and only provided for backwards --- compatibility. New code should use 'Uniform' and 'UniformRange' instead. +-- * The type class 'Random' is only provided for backwards compatibility. +-- New code should use 'Uniform' and 'UniformRange' instead. -- -- * The methods 'next' and 'genRange' in 'RandomGen' are deprecated and only -- provided for backwards compatibility. New instances of 'RandomGen' should @@ -1650,108 +432,3 @@ getStdRandom f = atomicModifyIORef' theStdGen (swap . f) -- International Conference on Object Oriented Programming Systems Languages & -- Applications (OOPSLA '14). ACM, New York, NY, USA, 453-472. DOI: -- - --- Appendix 1. --- --- @top@ and @bottom@ are signed integers of bit width @n@. @toUnsigned@ --- converts a signed integer to an unsigned number of the same bit width @n@. --- --- range = toUnsigned top - toUnsigned bottom --- --- This works out correctly thanks to modular arithmetic. Conceptually, --- --- toUnsigned x | x >= 0 = x --- toUnsigned x | x < 0 = 2^n + x --- --- The following combinations are possible: --- --- 1. @bottom >= 0@ and @top >= 0@ --- 2. @bottom < 0@ and @top >= 0@ --- 3. @bottom < 0@ and @top < 0@ --- --- Note that @bottom >= 0@ and @top < 0@ is impossible because of the --- invariant @bottom < top@. --- --- For any signed integer @i@ of width @n@, we have: --- --- -2^(n-1) <= i <= 2^(n-1) - 1 --- --- Considering each combination in turn, we have --- --- 1. @bottom >= 0@ and @top >= 0@ --- --- range = (toUnsigned top - toUnsigned bottom) `mod` 2^n --- --^ top >= 0, so toUnsigned top == top --- --^ bottom >= 0, so toUnsigned bottom == bottom --- = (top - bottom) `mod` 2^n --- --^ top <= 2^(n-1) - 1 and bottom >= 0 --- --^ top - bottom <= 2^(n-1) - 1 --- --^ 0 < top - bottom <= 2^(n-1) - 1 --- = top - bottom --- --- 2. @bottom < 0@ and @top >= 0@ --- --- range = (toUnsigned top - toUnsigned bottom) `mod` 2^n --- --^ top >= 0, so toUnsigned top == top --- --^ bottom < 0, so toUnsigned bottom == 2^n + bottom --- = (top - (2^n + bottom)) `mod` 2^n --- --^ summand -2^n cancels out in calculation modulo 2^n --- = (top - bottom) `mod` 2^n --- --^ top <= 2^(n-1) - 1 and bottom >= -2^(n-1) --- --^ top - bottom <= (2^(n-1) - 1) - (-2^(n-1)) = 2^n - 1 --- --^ 0 < top - bottom <= 2^n - 1 --- = top - bottom --- --- 3. @bottom < 0@ and @top < 0@ --- --- range = (toUnsigned top - toUnsigned bottom) `mod` 2^n --- --^ top < 0, so toUnsigned top == 2^n + top --- --^ bottom < 0, so toUnsigned bottom == 2^n + bottom --- = ((2^n + top) - (2^n + bottom)) `mod` 2^n --- --^ summand 2^n cancels out in calculation modulo 2^n --- = (top - bottom) `mod` 2^n --- --^ top <= -1 --- --^ bottom >= -2^(n-1) --- --^ top - bottom <= -1 - (-2^(n-1)) = 2^(n-1) - 1 --- --^ 0 < top - bottom <= 2^(n-1) - 1 --- = top - bottom - --- $setup --- >>> import Control.Arrow (first, second) --- >>> import Control.Monad (replicateM) --- >>> import Control.Monad.Primitive --- >>> import Data.Bits --- >>> import Data.Int (Int32) --- >>> import Data.Word (Word8, Word16, Word32, Word64) --- >>> import System.IO (IOMode(WriteMode), withBinaryFile) --- >>> import qualified System.Random.MWC as MWC --- --- >>> :set -XFlexibleContexts --- >>> :set -XFlexibleInstances --- >>> :set -XMultiParamTypeClasses --- >>> :set -XTypeFamilies --- >>> :set -XUndecidableInstances --- --- >>> :set -fno-warn-missing-methods --- --- >>> :{ --- let buildWord64 :: Word32 -> Word32 -> Word64 --- buildWord64 x y = (fromIntegral x `shiftL` 32) .|. fromIntegral y --- :} --- --- >>> :{ --- instance (s ~ PrimState m, PrimMonad m) => MonadRandom MWC.Gen s m where --- newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } --- thawGen = fmap MWC.restore unFrozen --- freezeGen = fmap Frozen . MWC.save --- uniformWord8 = MWC.uniform --- uniformWord16 = MWC.uniform --- uniformWord32 = MWC.uniform --- uniformWord64 = MWC.uniform --- uniformShortByteString n g = unsafeSTToPrim (genShortByteStringST n (MWC.uniform g)) --- :} --- --- >>> :{ --- let rolls :: MonadRandom g s m => Int -> g s -> m [Word8] --- rolls n = replicateM n . uniformRM (1, 6) --- :} diff --git a/System/Random/Internal.hs b/System/Random/Internal.hs new file mode 100644 index 000000000..d477c9489 --- /dev/null +++ b/System/Random/Internal.hs @@ -0,0 +1,962 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE CPP #-} +{-# LANGUAGE DefaultSignatures #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE FunctionalDependencies #-} +{-# LANGUAGE GHCForeignImportPrim #-} +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE Trustworthy #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE UnboxedTuples #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE UnliftedFFITypes #-} +{-# OPTIONS_HADDOCK hide, not-home #-} +#include "MachDeps.h" + +-- | +-- Module : System.Random.Internal +-- Copyright : (c) The University of Glasgow 2001 +-- License : BSD-style (see the file LICENSE in the 'random' repository) +-- Maintainer : libraries@haskell.org +-- Stability : stable +-- +-- This library deals with the common task of pseudo-random number generation. +module System.Random.Internal + (-- * Pure and monadic pseudo-random number generator interfaces + RandomGen(..) + , MonadRandom(..) + , Frozen(..) + + -- ** Standard pseudo-random number generator + , StdGen + , mkStdGen + + -- * Monadic adapters for pure pseudo-random number generators + -- ** Pure adapter + , PureGen + , splitGen + , runGenState + , runGenState_ + , runGenStateT + , runGenStateT_ + , runPureGenST + + -- * Pseudo-random values of various types + , Uniform(..) + , UniformRange(..) + , uniformByteString + + -- * Generators for sequences of pseudo-random bytes + , genShortByteStringIO + , genShortByteStringST + ) where + +import Control.Arrow +import Control.Monad.IO.Class +import Control.Monad.ST +import Control.Monad.ST.Unsafe +import Control.Monad.State.Strict +import Data.Bits +import Data.ByteString.Builder.Prim (word64LE) +import Data.ByteString.Builder.Prim.Internal (runF) +import Data.ByteString.Internal (ByteString(PS)) +import Data.ByteString.Short.Internal (ShortByteString(SBS), fromShort) +import Data.Int +import Data.Word +import Foreign.C.Types +import Foreign.Marshal.Alloc (alloca) +import Foreign.Ptr (plusPtr) +import Foreign.Storable (peekByteOff, pokeByteOff) +import GHC.Exts +import GHC.ForeignPtr +import System.IO.Unsafe (unsafePerformIO) +import qualified System.Random.SplitMix as SM +import qualified System.Random.SplitMix32 as SM32 +import GHC.Word +import GHC.IO (IO(..)) + +-- | 'RandomGen' is an interface to pure pseudo-random number generators. +-- +-- 'StdGen' is the standard 'RandomGen' instance provided by this library. +{-# DEPRECATED next "No longer used" #-} +{-# DEPRECATED genRange "No longer used" #-} +class RandomGen g where + {-# MINIMAL split,(genWord32|genWord64|(next,genRange)) #-} + -- | Returns an 'Int' that is uniformly distributed over the range returned by + -- 'genRange' (including both end points), and a new generator. Using 'next' + -- is inefficient as all operations go via 'Integer'. See + -- [here](https://alexey.kuleshevi.ch/blog/2019/12/21/random-benchmarks) for + -- more details. It is thus deprecated. + next :: g -> (Int, g) + next g = runGenState g (uniformRM (genRange g)) + + -- | Returns a 'Word8' that is uniformly distributed over the entire 'Word8' + -- range. + -- + -- @since 1.2 + genWord8 :: g -> (Word8, g) + genWord8 = first fromIntegral . genWord32 + + -- | Returns a 'Word16' that is uniformly distributed over the entire 'Word16' + -- range. + -- + -- @since 1.2 + genWord16 :: g -> (Word16, g) + genWord16 = first fromIntegral . genWord32 + + -- | Returns a 'Word32' that is uniformly distributed over the entire 'Word32' + -- range. + -- + -- @since 1.2 + genWord32 :: g -> (Word32, g) + genWord32 = randomIvalIntegral (minBound, maxBound) + -- Once `next` is removed, this implementation should be used instead: + -- first fromIntegral . genWord64 + + -- | Returns a 'Word64' that is uniformly distributed over the entire 'Word64' + -- range. + -- + -- @since 1.2 + genWord64 :: g -> (Word64, g) + genWord64 g = + case genWord32 g of + (l32, g') -> + case genWord32 g' of + (h32, g'') -> + ((fromIntegral h32 `unsafeShiftL` 32) .|. fromIntegral l32, g'') + + -- | @genWord32R upperBound g@ returns a 'Word32' that is uniformly + -- distributed over the range @[0, upperBound]@. + -- + -- @since 1.2 + genWord32R :: Word32 -> g -> (Word32, g) + genWord32R m g = runGenState g (unbiasedWordMult32 m) + + -- | @genWord64R upperBound g@ returns a 'Word64' that is uniformly + -- distributed over the range @[0, upperBound]@. + -- + -- @since 1.2 + genWord64R :: Word64 -> g -> (Word64, g) + genWord64R m g = runGenState g (unsignedBitmaskWithRejectionM uniformWord64 m) + + -- | @genShortByteString n g@ returns a 'ShortByteString' of length @n@ + -- filled with pseudo-random bytes. + -- + -- @since 1.2 + genShortByteString :: Int -> g -> (ShortByteString, g) + genShortByteString n g = + unsafePerformIO $ runGenStateT g (genShortByteStringIO n . uniformWord64) + {-# INLINE genShortByteString #-} + + -- | Yields the range of values returned by 'next'. + -- + -- It is required that: + -- + -- * If @(a, b) = 'genRange' g@, then @a < b@. + -- * 'genRange' must not examine its argument so the value it returns is + -- determined only by the instance of 'RandomGen'. + -- + -- The default definition spans the full range of 'Int'. + genRange :: g -> (Int, Int) + genRange _ = (minBound, maxBound) + + -- | Returns two distinct pseudo-random number generators. + -- + -- Implementations should take care to ensure that the resulting generators + -- are not correlated. Some pseudo-random number generators are not + -- splittable. In that case, the 'split' implementation should fail with a + -- descriptive 'error' message. + split :: g -> (g, g) + + +-- | 'MonadRandom' is an interface to monadic pseudo-random number generators. +class Monad m => MonadRandom g s m | g m -> s where + -- | Represents the state of the pseudo-random number generator for use with + -- 'thawGen' and 'freezeGen'. + -- + -- @since 1.2 + data Frozen g :: * + {-# MINIMAL freezeGen,thawGen,(uniformWord32|uniformWord64) #-} + + -- | Restores the pseudo-random number generator from its 'Frozen' + -- representation. + -- + -- @since 1.2 + thawGen :: Frozen g -> m (g s) + + -- | Saves the state of the pseudo-random number generator to its 'Frozen' + -- representation. + -- + -- @since 1.2 + freezeGen :: g s -> m (Frozen g) + + -- | @uniformWord32R upperBound g@ generates a 'Word32' that is uniformly + -- distributed over the range @[0, upperBound]@. + -- + -- @since 1.2 + uniformWord32R :: Word32 -> g s -> m Word32 + uniformWord32R = unsignedBitmaskWithRejectionM uniformWord32 + + -- | @uniformWord64R upperBound g@ generates a 'Word64' that is uniformly + -- distributed over the range @[0, upperBound]@. + -- + -- @since 1.2 + uniformWord64R :: Word64 -> g s -> m Word64 + uniformWord64R = unsignedBitmaskWithRejectionM uniformWord64 + + -- | Generates a 'Word8' that is uniformly distributed over the entire 'Word8' + -- range. + -- + -- The default implementation extracts a 'Word8' from 'uniformWord32'. + -- + -- @since 1.2 + uniformWord8 :: g s -> m Word8 + uniformWord8 = fmap fromIntegral . uniformWord32 + + -- | Generates a 'Word16' that is uniformly distributed over the entire + -- 'Word16' range. + -- + -- The default implementation extracts a 'Word16' from 'uniformWord32'. + -- + -- @since 1.2 + uniformWord16 :: g s -> m Word16 + uniformWord16 = fmap fromIntegral . uniformWord32 + + -- | Generates a 'Word32' that is uniformly distributed over the entire + -- 'Word32' range. + -- + -- The default implementation extracts a 'Word32' from 'uniformWord64'. + -- + -- @since 1.2 + uniformWord32 :: g s -> m Word32 + uniformWord32 = fmap fromIntegral . uniformWord64 + + -- | Generates a 'Word64' that is uniformly distributed over the entire + -- 'Word64' range. + -- + -- The default implementation combines two 'Word32' from 'uniformWord32' into + -- one 'Word64'. + -- + -- @since 1.2 + uniformWord64 :: g s -> m Word64 + uniformWord64 g = do + l32 <- uniformWord32 g + h32 <- uniformWord32 g + pure (unsafeShiftL (fromIntegral h32) 32 .|. fromIntegral l32) + + -- | @uniformShortByteString n g@ generates a 'ShortByteString' of length @n@ + -- filled with pseudo-random bytes. + -- + -- @since 1.2 + uniformShortByteString :: Int -> g s -> m ShortByteString + default uniformShortByteString :: MonadIO m => Int -> g s -> m ShortByteString + uniformShortByteString n = genShortByteStringIO n . uniformWord64 + {-# INLINE uniformShortByteString #-} + + + +data MBA s = MBA (MutableByteArray# s) + + +-- | Efficiently generates a sequence of pseudo-random bytes in a platform +-- independent manner. The allocated memory is be pinned, so it is safe to use +-- with FFI calls. +-- +-- @since 1.2 +genShortByteStringIO :: MonadIO m => Int -> m Word64 -> m ShortByteString +genShortByteStringIO n0 gen64 = do + let !n@(I# n#) = max 0 n0 + (n64, nrem64) = n `quotRem` 8 + MBA mba# <- + liftIO $ + IO $ \s# -> + case newPinnedByteArray# n# s# of + (# s'#, mba# #) -> (# s'#, MBA mba# #) + let go i ptr + | i < n64 = do + w64 <- gen64 + -- Writing 8 bytes at a time in a Little-endian order gives us + -- platform portability + liftIO $ runF word64LE w64 ptr + go (i + 1) (ptr `plusPtr` 8) + | otherwise = return ptr + ptr <- go 0 (Ptr (byteArrayContents# (unsafeCoerce# mba#))) + when (nrem64 > 0) $ do + w64 <- gen64 + -- In order to not mess up the byte order we write generated Word64 into a + -- temporary pointer and then copy only the missing bytes over to the array. + -- It is tempting to simply generate as many bytes as we still need using + -- smaller generators (eg. uniformWord8), but that would result in + -- inconsistent tail when total length is slightly varied. + liftIO $ + alloca $ \w64ptr -> do + runF word64LE w64 w64ptr + forM_ [0 .. nrem64 - 1] $ \i -> do + w8 :: Word8 <- peekByteOff w64ptr i + pokeByteOff ptr i w8 + liftIO $ + IO $ \s# -> + case unsafeFreezeByteArray# mba# s# of + (# s'#, ba# #) -> (# s'#, SBS ba# #) +{-# INLINE genShortByteStringIO #-} + +-- | Same as 'genShortByteStringIO', but runs in 'ST'. +-- +-- @since 1.2 +genShortByteStringST :: Int -> ST s Word64 -> ST s ShortByteString +genShortByteStringST n action = + unsafeIOToST (genShortByteStringIO n (unsafeSTToIO action)) + +pinnedByteArrayToByteString :: ByteArray# -> ByteString +pinnedByteArrayToByteString ba# = + PS (pinnedByteArrayToForeignPtr ba#) 0 (I# (sizeofByteArray# ba#)) +{-# INLINE pinnedByteArrayToByteString #-} + +pinnedByteArrayToForeignPtr :: ByteArray# -> ForeignPtr a +pinnedByteArrayToForeignPtr ba# = + ForeignPtr (byteArrayContents# ba#) (PlainPtr (unsafeCoerce# ba#)) +{-# INLINE pinnedByteArrayToForeignPtr #-} + + +-- | Generates a pseudo-random 'ByteString' of the specified size. +-- +-- @since 1.2 +uniformByteString :: MonadRandom g s m => Int -> g s -> m ByteString +uniformByteString n g = do + ba@(SBS ba#) <- uniformShortByteString n g + pure $ + if isTrue# (isByteArrayPinned# ba#) + then pinnedByteArrayToByteString ba# + else fromShort ba +{-# INLINE uniformByteString #-} + + +-- | Opaque data type that carries the type of a pure pseudo-random number +-- generator. +-- +-- @since 1.2 +data PureGen g s = PureGenI + +instance (RandomGen g, MonadState g m) => MonadRandom (PureGen g) g m where + newtype Frozen (PureGen g) = PureGen g + thawGen (PureGen g) = PureGenI <$ put g + freezeGen _ = fmap PureGen get + uniformWord32R r _ = state (genWord32R r) + uniformWord64R r _ = state (genWord64R r) + uniformWord8 _ = state genWord8 + uniformWord16 _ = state genWord16 + uniformWord32 _ = state genWord32 + uniformWord64 _ = state genWord64 + uniformShortByteString n _ = state (genShortByteString n) + + + +-- | Splits a pseudo-random number generator into two. Updates the state with +-- one of the resulting generators and returns the other. +-- +-- @since 1.2 +splitGen :: (MonadState g m, RandomGen g) => m g +splitGen = state split + +-- | Runs a monadic generating action in the `State` monad using a pure +-- pseudo-random number generator. +-- +-- @since 1.2 +runGenState :: RandomGen g => g -> (PureGen g g -> State g a) -> (a, g) +runGenState g f = runState (f PureGenI) g + +-- | Runs a monadic generating action in the `State` monad using a pure +-- pseudo-random number generator. Returns only the resulting pseudo-random +-- value. +-- +-- @since 1.2 +runGenState_ :: RandomGen g => g -> (PureGen g g -> State g a) -> a +runGenState_ g = fst . runGenState g + +-- | Runs a monadic generating action in the `StateT` monad using a pure +-- pseudo-random number generator. +-- +-- @since 1.2 +runGenStateT :: RandomGen g => g -> (PureGen g g -> StateT g m a) -> m (a, g) +runGenStateT g f = runStateT (f PureGenI) g + +-- | Runs a monadic generating action in the `StateT` monad using a pure +-- pseudo-random number generator. Returns only the resulting pseudo-random +-- value. +-- +-- @since 1.2 +runGenStateT_ :: (RandomGen g, Functor f) => g -> (PureGen g g -> StateT g f a) -> f a +runGenStateT_ g = fmap fst . runGenStateT g + +-- | Runs a monadic generating action in the `ST` monad using a pure +-- pseudo-random number generator. +-- +-- @since 1.2 +runPureGenST :: RandomGen g => g -> (forall s . PureGen g g -> StateT g (ST s) a) -> (a, g) +runPureGenST g action = runST $ runGenStateT g $ action +{-# INLINE runPureGenST #-} + + +-- | The standard pseudo-random number generator. +type StdGen = SM.SMGen + +instance RandomGen StdGen where + next = SM.nextInt + genWord32 = SM.nextWord32 + genWord64 = SM.nextWord64 + split = SM.splitSMGen + +instance RandomGen SM32.SMGen where + next = SM32.nextInt + genWord32 = SM32.nextWord32 + genWord64 = SM32.nextWord64 + split = SM32.splitSMGen + +-- | Constructs a 'StdGen' deterministically. +mkStdGen :: Int -> StdGen +mkStdGen s = SM.mkSMGen $ fromIntegral s + +-- | Generates a value uniformly distributed over all possible values of that +-- datatype. +-- +-- @since 1.2 +class Uniform a where + uniformM :: MonadRandom g s m => g s -> m a + +-- | Generates a value uniformly distributed over the provided inclusive range. +-- +-- For example, @uniformR (1,4)@ should generate values uniformly from the set +-- @[1,2,3,4]@. +-- +-- The API uses an inclusive range so any range can be expressed, even when +-- using fixed-size ints, enumerations etc. +-- +-- The following law should hold to make the function always defined: +-- +-- > uniformRM (a,b) = uniformM (b,a) +-- +-- @since 1.2 +class UniformRange a where + uniformRM :: MonadRandom g s m => (a, a) -> g s -> m a + +instance UniformRange Integer where + uniformRM = uniformIntegerM + +instance Uniform Int8 where + uniformM = fmap (fromIntegral :: Word8 -> Int8) . uniformWord8 +instance UniformRange Int8 where + uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int8 -> Word8) fromIntegral + +instance Uniform Int16 where + uniformM = fmap (fromIntegral :: Word16 -> Int16) . uniformWord16 +instance UniformRange Int16 where + uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int16 -> Word16) fromIntegral + +instance Uniform Int32 where + uniformM = fmap (fromIntegral :: Word32 -> Int32) . uniformWord32 +instance UniformRange Int32 where + uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int32 -> Word32) fromIntegral + +instance Uniform Int64 where + uniformM = fmap (fromIntegral :: Word64 -> Int64) . uniformWord64 +instance UniformRange Int64 where + uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int64 -> Word64) fromIntegral + +instance Uniform Int where +#if WORD_SIZE_IN_BITS < 64 + uniformM = fmap (fromIntegral :: Word32 -> Int) . uniformWord32 +#else + uniformM = fmap (fromIntegral :: Word64 -> Int) . uniformWord64 +#endif + {-# INLINE uniformM #-} +instance UniformRange Int where + uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int -> Word) fromIntegral + {-# INLINE uniformRM #-} + +instance Uniform Word where +#if WORD_SIZE_IN_BITS < 64 + uniformM = fmap (fromIntegral :: Word32 -> Word) . uniformWord32 +#else + uniformM = fmap (fromIntegral :: Word64 -> Word) . uniformWord64 +#endif +instance UniformRange Word where + {-# INLINE uniformRM #-} + uniformRM = unsignedBitmaskWithRejectionRM + +instance Uniform Word8 where + {-# INLINE uniformM #-} + uniformM = uniformWord8 +instance UniformRange Word8 where + {-# INLINE uniformRM #-} + uniformRM = unsignedBitmaskWithRejectionRM + +instance Uniform Word16 where + {-# INLINE uniformM #-} + uniformM = uniformWord16 +instance UniformRange Word16 where + {-# INLINE uniformRM #-} + uniformRM = unsignedBitmaskWithRejectionRM + +instance Uniform Word32 where + {-# INLINE uniformM #-} + uniformM = uniformWord32 +instance UniformRange Word32 where + {-# INLINE uniformRM #-} + uniformRM (b, t) g | b > t = (+t) <$> unbiasedWordMult32 (b - t) g + | otherwise = (+b) <$> unbiasedWordMult32 (t - b) g + +instance Uniform Word64 where + {-# INLINE uniformM #-} + uniformM = uniformWord64 +instance UniformRange Word64 where + {-# INLINE uniformRM #-} + uniformRM = unsignedBitmaskWithRejectionRM + +instance Uniform CBool where + uniformM = fmap CBool . uniformM +instance UniformRange CBool where + uniformRM (CBool b, CBool t) = fmap CBool . uniformRM (b, t) + +instance Uniform CChar where + uniformM = fmap CChar . uniformM +instance UniformRange CChar where + uniformRM (CChar b, CChar t) = fmap CChar . uniformRM (b, t) + +instance Uniform CSChar where + uniformM = fmap CSChar . uniformM +instance UniformRange CSChar where + uniformRM (CSChar b, CSChar t) = fmap CSChar . uniformRM (b, t) + +instance Uniform CUChar where + uniformM = fmap CUChar . uniformM +instance UniformRange CUChar where + uniformRM (CUChar b, CUChar t) = fmap CUChar . uniformRM (b, t) + +instance Uniform CShort where + uniformM = fmap CShort . uniformM +instance UniformRange CShort where + uniformRM (CShort b, CShort t) = fmap CShort . uniformRM (b, t) + +instance Uniform CUShort where + uniformM = fmap CUShort . uniformM +instance UniformRange CUShort where + uniformRM (CUShort b, CUShort t) = fmap CUShort . uniformRM (b, t) + +instance Uniform CInt where + uniformM = fmap CInt . uniformM +instance UniformRange CInt where + uniformRM (CInt b, CInt t) = fmap CInt . uniformRM (b, t) + +instance Uniform CUInt where + uniformM = fmap CUInt . uniformM +instance UniformRange CUInt where + uniformRM (CUInt b, CUInt t) = fmap CUInt . uniformRM (b, t) + +instance Uniform CLong where + uniformM = fmap CLong . uniformM +instance UniformRange CLong where + uniformRM (CLong b, CLong t) = fmap CLong . uniformRM (b, t) + +instance Uniform CULong where + uniformM = fmap CULong . uniformM +instance UniformRange CULong where + uniformRM (CULong b, CULong t) = fmap CULong . uniformRM (b, t) + +instance Uniform CPtrdiff where + uniformM = fmap CPtrdiff . uniformM +instance UniformRange CPtrdiff where + uniformRM (CPtrdiff b, CPtrdiff t) = fmap CPtrdiff . uniformRM (b, t) + +instance Uniform CSize where + uniformM = fmap CSize . uniformM +instance UniformRange CSize where + uniformRM (CSize b, CSize t) = fmap CSize . uniformRM (b, t) + +instance Uniform CWchar where + uniformM = fmap CWchar . uniformM +instance UniformRange CWchar where + uniformRM (CWchar b, CWchar t) = fmap CWchar . uniformRM (b, t) + +instance Uniform CSigAtomic where + uniformM = fmap CSigAtomic . uniformM +instance UniformRange CSigAtomic where + uniformRM (CSigAtomic b, CSigAtomic t) = fmap CSigAtomic . uniformRM (b, t) + +instance Uniform CLLong where + uniformM = fmap CLLong . uniformM +instance UniformRange CLLong where + uniformRM (CLLong b, CLLong t) = fmap CLLong . uniformRM (b, t) + +instance Uniform CULLong where + uniformM = fmap CULLong . uniformM +instance UniformRange CULLong where + uniformRM (CULLong b, CULLong t) = fmap CULLong . uniformRM (b, t) + +instance Uniform CIntPtr where + uniformM = fmap CIntPtr . uniformM +instance UniformRange CIntPtr where + uniformRM (CIntPtr b, CIntPtr t) = fmap CIntPtr . uniformRM (b, t) + +instance Uniform CUIntPtr where + uniformM = fmap CUIntPtr . uniformM +instance UniformRange CUIntPtr where + uniformRM (CUIntPtr b, CUIntPtr t) = fmap CUIntPtr . uniformRM (b, t) + +instance Uniform CIntMax where + uniformM = fmap CIntMax . uniformM +instance UniformRange CIntMax where + uniformRM (CIntMax b, CIntMax t) = fmap CIntMax . uniformRM (b, t) + +instance Uniform CUIntMax where + uniformM = fmap CUIntMax . uniformM +instance UniformRange CUIntMax where + uniformRM (CUIntMax b, CUIntMax t) = fmap CUIntMax . uniformRM (b, t) + +instance UniformRange CFloat where + uniformRM (CFloat l, CFloat h) = fmap CFloat . uniformRM (l, h) + +instance UniformRange CDouble where + uniformRM (CDouble l, CDouble h) = fmap CDouble . uniformRM (l, h) + + +-- The `chr#` and `ord#` are the prim functions that will be called, regardless of which +-- way you gonna do the `Char` conversion, so it is better to call them directly and +-- bypass all the hoops. Also because `intToChar` and `charToInt` are internal functions +-- and are called on valid character ranges it is impossible to generate an invalid +-- `Char`, therefore it is totally fine to omit all the unnecessary checks involved in +-- other paths of conversion. +word32ToChar :: Word32 -> Char +word32ToChar (W32# w#) = C# (chr# (word2Int# w#)) +{-# INLINE word32ToChar #-} + +charToWord32 :: Char -> Word32 +charToWord32 (C# c#) = W32# (int2Word# (ord# c#)) +{-# INLINE charToWord32 #-} + +instance Uniform Char where + uniformM g = word32ToChar <$> unsignedBitmaskWithRejectionM uniformM (charToWord32 maxBound) g + {-# INLINE uniformM #-} +instance UniformRange Char where + uniformRM (l, h) g = + word32ToChar <$> unsignedBitmaskWithRejectionRM (charToWord32 l, charToWord32 h) g + {-# INLINE uniformRM #-} + +instance Uniform Bool where + uniformM = fmap wordToBool . uniformWord8 + where wordToBool w = (w .&. 1) /= 0 +instance UniformRange Bool where + uniformRM (False, False) _g = return False + uniformRM (True, True) _g = return True + uniformRM _ g = uniformM g + +instance UniformRange Double where + uniformRM (l, h) g = do + w64 <- uniformWord64 g + let x = word64ToDoubleInUnitInterval w64 + return $ (h - l) * x + l + +-- | Turns a given uniformly distributed 'Word64' value into a uniformly +-- distributed 'Double' value in the range [0, 1). +word64ToDoubleInUnitInterval :: Word64 -> Double +word64ToDoubleInUnitInterval w64 = between1and2 - 1.0 + where + between1and2 = castWord64ToDouble $ (w64 `unsafeShiftR` 12) .|. 0x3ff0000000000000 +{-# INLINE word64ToDoubleInUnitInterval #-} + +-- | These are now in 'GHC.Float' but unpatched in some versions so +-- for now we roll our own. See +-- https://gitlab.haskell.org/ghc/ghc/-/blob/6d172e63f3dd3590b0a57371efb8f924f1fcdf05/libraries/base/GHC/Float.hs +{-# INLINE castWord32ToFloat #-} +castWord32ToFloat :: Word32 -> Float +castWord32ToFloat (W32# w#) = F# (stgWord32ToFloat w#) + +foreign import prim "stg_word32ToFloatyg" + stgWord32ToFloat :: Word# -> Float# + +{-# INLINE castWord64ToDouble #-} +castWord64ToDouble :: Word64 -> Double +castWord64ToDouble (W64# w) = D# (stgWord64ToDouble w) + +foreign import prim "stg_word64ToDoubleyg" +#if WORD_SIZE_IN_BITS == 64 + stgWord64ToDouble :: Word# -> Double# +#else + stgWord64ToDouble :: Word64# -> Double# +#endif + + +instance UniformRange Float where + uniformRM (l, h) g = do + w32 <- uniformWord32 g + let x = word32ToFloatInUnitInterval w32 + return $ (h - l) * x + l + +-- | Turns a given uniformly distributed 'Word32' value into a uniformly +-- distributed 'Float' value in the range [0,1). +word32ToFloatInUnitInterval :: Word32 -> Float +word32ToFloatInUnitInterval w32 = between1and2 - 1.0 + where + between1and2 = castWord32ToFloat $ (w32 `unsafeShiftR` 9) .|. 0x3f800000 +{-# INLINE word32ToFloatInUnitInterval #-} + +-- The two integer functions below take an [inclusive,inclusive] range. +randomIvalIntegral :: (RandomGen g, Integral a) => (a, a) -> g -> (a, g) +randomIvalIntegral (l,h) = randomIvalInteger (toInteger l, toInteger h) + +{-# SPECIALIZE randomIvalInteger :: (Num a) => + (Integer, Integer) -> StdGen -> (a, StdGen) #-} + +randomIvalInteger :: (RandomGen g, Num a) => (Integer, Integer) -> g -> (a, g) +randomIvalInteger (l,h) rng + | l > h = randomIvalInteger (h,l) rng + | otherwise = case (f 1 0 rng) of (v, rng') -> (fromInteger (l + v `mod` k), rng') + where + (genlo, genhi) = genRange rng + b = fromIntegral genhi - fromIntegral genlo + 1 + + -- Probabilities of the most likely and least likely result + -- will differ at most by a factor of (1 +- 1/q). Assuming the RandomGen + -- is uniform, of course + + -- On average, log q / log b more pseudo-random values will be generated + -- than the minimum + q = 1000 + k = h - l + 1 + magtgt = k * q + + -- generate pseudo-random values until we exceed the target magnitude + f mag v g | mag >= magtgt = (v, g) + | otherwise = v' `seq`f (mag*b) v' g' where + (x,g') = next g + v' = (v * b + (fromIntegral x - fromIntegral genlo)) + +-- | Generate an 'Integer' in the range @[l, h]@ if @l <= h@ and @[h, l]@ +-- otherwise. +uniformIntegerM :: (MonadRandom g s m) => (Integer, Integer) -> g s -> m Integer +uniformIntegerM (l, h) gen = case l `compare` h of + LT -> do + let limit = h - l + let limitAsWord64 :: Word64 = fromIntegral limit + bounded <- + if (toInteger limitAsWord64) == limit + -- Optimisation: if 'limit' fits into 'Word64', generate a bounded + -- 'Word64' and then convert to 'Integer' + then toInteger <$> unsignedBitmaskWithRejectionM uniformWord64 limitAsWord64 gen + else boundedExclusiveIntegerM (limit + 1) gen + return $ l + bounded + GT -> uniformIntegerM (h, l) gen + EQ -> pure l +{-# INLINE uniformIntegerM #-} + +-- | Generate an 'Integer' in the range @[0, s)@ using a variant of Lemire's +-- multiplication method. +-- +-- Daniel Lemire. 2019. Fast Random Integer Generation in an Interval. In ACM +-- Transactions on Modeling and Computer Simulation +-- https://doi.org/10.1145/3230636 +-- +-- PRECONDITION (unchecked): s > 0 +boundedExclusiveIntegerM :: (MonadRandom g s m) => Integer -> g s -> m Integer +boundedExclusiveIntegerM s gen = go + where + n = integerWordSize s + -- We renamed 'L' from the paper to 'k' here because 'L' is not a valid + -- variable name in Haskell and 'l' is already used in the algorithm. + k = WORD_SIZE_IN_BITS * n + twoToK = (1::Integer) `shiftL` k + modTwoToKMask = twoToK - 1 + + t = (twoToK - s) `mod` s + go = do + x <- uniformIntegerWords n gen + let m = x * s + -- m .&. modTwoToKMask == m `mod` twoToK + let l = m .&. modTwoToKMask + if l < t + then go + -- m `shiftR` k == m `quot` twoToK + else return $ m `shiftR` k +{-# INLINE boundedExclusiveIntegerM #-} + +-- | @integerWordSize i@ returns that least @w@ such that +-- @i <= WORD_SIZE_IN_BITS^w@. +integerWordSize :: Integer -> Int +integerWordSize = go 0 + where + go !acc i + | i == 0 = acc + | otherwise = go (acc + 1) (i `shiftR` WORD_SIZE_IN_BITS) +{-# INLINE integerWordSize #-} + +-- | @uniformIntegerWords n@ is a uniformly pseudo-random 'Integer' in the range +-- @[0, WORD_SIZE_IN_BITS^n)@. +uniformIntegerWords :: (MonadRandom g s m) => Int -> g s -> m Integer +uniformIntegerWords n gen = go 0 n + where + go !acc i + | i == 0 = return acc + | otherwise = do + (w :: Word) <- uniformM gen + go ((acc `shiftL` WORD_SIZE_IN_BITS) .|. (fromIntegral w)) (i - 1) +{-# INLINE uniformIntegerWords #-} + +-- | Uniformly generate Word32 in @[0, s]@. +unbiasedWordMult32 :: MonadRandom g s m => Word32 -> g s -> m Word32 +unbiasedWordMult32 s g + | s == maxBound = uniformWord32 g + | otherwise = unbiasedWordMult32Exclusive (s+1) g +{-# INLINE unbiasedWordMult32 #-} + +-- | See [Lemire's paper](https://arxiv.org/pdf/1805.10941.pdf), +-- [O\'Neill's +-- blogpost](https://www.pcg-random.org/posts/bounded-rands.html) and +-- more directly [O\'Neill's github +-- repo](https://github.com/imneme/bounded-rands/blob/3d71f53c975b1e5b29f2f3b05a74e26dab9c3d84/bounded32.cpp#L234). +-- N.B. The range is [0,t) **not** [0,t]. +unbiasedWordMult32Exclusive :: MonadRandom g s m => Word32 -> g s -> m Word32 +unbiasedWordMult32Exclusive r g = go + where + t :: Word32 + t = (-r) `mod` r -- Calculates 2^32 `mod` r!!! + go = do + x <- uniformWord32 g + let m :: Word64 + m = (fromIntegral x) * (fromIntegral r) + l :: Word32 + l = fromIntegral m + if (l >= t) then return (fromIntegral $ m `shiftR` 32) else go + +-- | This only works for unsigned integrals +unsignedBitmaskWithRejectionRM :: + (MonadRandom g s m, FiniteBits a, Num a, Ord a, Uniform a) + => (a, a) + -> g s + -> m a +unsignedBitmaskWithRejectionRM (bottom, top) gen + | bottom > top = unsignedBitmaskWithRejectionRM (top, bottom) gen + | bottom == top = pure top + | otherwise = (bottom +) <$> unsignedBitmaskWithRejectionM uniformM range gen + where + range = top - bottom +{-# INLINE unsignedBitmaskWithRejectionRM #-} + +-- | This works for signed integrals by explicit conversion to unsigned and abusing overflow +signedBitmaskWithRejectionRM :: + (Num a, Num b, Ord b, Ord a, FiniteBits a, MonadRandom g s f, Uniform a) + => (b -> a) + -> (a -> b) + -> (b, b) + -> g s + -> f b +signedBitmaskWithRejectionRM toUnsigned fromUnsigned (bottom, top) gen + | bottom > top = signedBitmaskWithRejectionRM toUnsigned fromUnsigned (top, bottom) gen + | bottom == top = pure top + | otherwise = (bottom +) . fromUnsigned <$> + unsignedBitmaskWithRejectionM uniformM range gen + where + -- This works in all cases, see Appendix 1 at the end of the file. + range = toUnsigned top - toUnsigned bottom +{-# INLINE signedBitmaskWithRejectionRM #-} + +unsignedBitmaskWithRejectionM :: (Ord a, FiniteBits a, Num a, MonadRandom g s m) => (g s -> m a) -> a -> g s -> m a +unsignedBitmaskWithRejectionM genUniformM range gen = go + where + mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1) + go = do + x <- genUniformM gen + let x' = x .&. mask + if x' > range + then go + else pure x' +{-# INLINE unsignedBitmaskWithRejectionM #-} + +------------------------------------------------------------------------------- +-- 'Uniform' instances for tuples +------------------------------------------------------------------------------- + +instance (Uniform a, Uniform b) => Uniform (a, b) where + uniformM g = (,) <$> uniformM g <*> uniformM g + +instance (Uniform a, Uniform b, Uniform c) => Uniform (a, b, c) where + uniformM g = (,,) <$> uniformM g <*> uniformM g <*> uniformM g + +instance (Uniform a, Uniform b, Uniform c, Uniform d) => Uniform (a, b, c, d) where + uniformM g = (,,,) <$> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g + +instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e) => Uniform (a, b, c, d, e) where + uniformM g = (,,,,) <$> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g + +instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e, Uniform f) => Uniform (a, b, c, d, e, f) where + uniformM g = (,,,,,) <$> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g + +instance (Uniform a, Uniform b, Uniform c, Uniform d, Uniform e, Uniform f, Uniform g) => Uniform (a, b, c, d, e, f, g) where + uniformM g = (,,,,,,) <$> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g <*> uniformM g + +-- Appendix 1. +-- +-- @top@ and @bottom@ are signed integers of bit width @n@. @toUnsigned@ +-- converts a signed integer to an unsigned number of the same bit width @n@. +-- +-- range = toUnsigned top - toUnsigned bottom +-- +-- This works out correctly thanks to modular arithmetic. Conceptually, +-- +-- toUnsigned x | x >= 0 = x +-- toUnsigned x | x < 0 = 2^n + x +-- +-- The following combinations are possible: +-- +-- 1. @bottom >= 0@ and @top >= 0@ +-- 2. @bottom < 0@ and @top >= 0@ +-- 3. @bottom < 0@ and @top < 0@ +-- +-- Note that @bottom >= 0@ and @top < 0@ is impossible because of the +-- invariant @bottom < top@. +-- +-- For any signed integer @i@ of width @n@, we have: +-- +-- -2^(n-1) <= i <= 2^(n-1) - 1 +-- +-- Considering each combination in turn, we have +-- +-- 1. @bottom >= 0@ and @top >= 0@ +-- +-- range = (toUnsigned top - toUnsigned bottom) `mod` 2^n +-- --^ top >= 0, so toUnsigned top == top +-- --^ bottom >= 0, so toUnsigned bottom == bottom +-- = (top - bottom) `mod` 2^n +-- --^ top <= 2^(n-1) - 1 and bottom >= 0 +-- --^ top - bottom <= 2^(n-1) - 1 +-- --^ 0 < top - bottom <= 2^(n-1) - 1 +-- = top - bottom +-- +-- 2. @bottom < 0@ and @top >= 0@ +-- +-- range = (toUnsigned top - toUnsigned bottom) `mod` 2^n +-- --^ top >= 0, so toUnsigned top == top +-- --^ bottom < 0, so toUnsigned bottom == 2^n + bottom +-- = (top - (2^n + bottom)) `mod` 2^n +-- --^ summand -2^n cancels out in calculation modulo 2^n +-- = (top - bottom) `mod` 2^n +-- --^ top <= 2^(n-1) - 1 and bottom >= -2^(n-1) +-- --^ top - bottom <= (2^(n-1) - 1) - (-2^(n-1)) = 2^n - 1 +-- --^ 0 < top - bottom <= 2^n - 1 +-- = top - bottom +-- +-- 3. @bottom < 0@ and @top < 0@ +-- +-- range = (toUnsigned top - toUnsigned bottom) `mod` 2^n +-- --^ top < 0, so toUnsigned top == 2^n + top +-- --^ bottom < 0, so toUnsigned bottom == 2^n + bottom +-- = ((2^n + top) - (2^n + bottom)) `mod` 2^n +-- --^ summand 2^n cancels out in calculation modulo 2^n +-- = (top - bottom) `mod` 2^n +-- --^ top <= -1 +-- --^ bottom >= -2^(n-1) +-- --^ top - bottom <= -1 - (-2^(n-1)) = 2^(n-1) - 1 +-- --^ 0 < top - bottom <= 2^(n-1) - 1 +-- = top - bottom diff --git a/System/Random/Monad.hs b/System/Random/Monad.hs new file mode 100644 index 000000000..7e9a08925 --- /dev/null +++ b/System/Random/Monad.hs @@ -0,0 +1,480 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE Safe #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE UndecidableInstances #-} + +-- | +-- Module : System.Random.Monad +-- Copyright : (c) The University of Glasgow 2001 +-- License : BSD-style (see the file LICENSE in the 'random' repository) +-- Maintainer : libraries@haskell.org +-- Stability : stable +-- +-- This library deals with the common task of pseudo-random number generation. +module System.Random.Monad + ( + -- * Pure Random Generator + module System.Random + -- * Monadic Random Generator + -- $introduction + + -- * Usage + -- ** How to generate pseudo-random values in monadic code + -- $usagemonadic + + -- ** How to generate pseudo-random values in pure code + -- $usagepure + + -- * Pure and monadic pseudo-random number generator interfaces + -- $interfaces + , MonadRandom(..) + , Frozen(..) + , runGenM + , runGenM_ + , RandomGenM(..) + , splitRandomGenM + + -- * Monadic adapters for pure pseudo-random number generators + -- $monadicadapters + + -- ** Pure adapter + , PureGen + , splitGen + , genRandom + , runGenState + , runGenState_ + , runGenStateT + , runGenStateT_ + , runPureGenST + -- ** Mutable adapter with atomic operations + , AtomicGen + , applyAtomicGen + -- ** Mutable adapter in 'IO' + , IOGen + , applyIOGen + -- ** Mutable adapter in 'ST' + , STGen + , applySTGen + , runSTGen + , runSTGen_ + + -- * Pseudo-random values of various types + -- $uniform + , Uniform(..) + , uniformListM + , UniformRange(..) + + -- * Generators for sequences of pseudo-random bytes + , genShortByteStringIO + , genShortByteStringST + , uniformByteString + + -- * Notes for pseudo-random number generator implementors + + -- ** How to implement 'MonadRandom' + -- $implementmonadrandom + + -- * References + -- $references + ) where + +import Control.Monad.IO.Class +import Control.Monad.ST +import Control.Monad.State.Strict +import Data.IORef +import Data.STRef +import System.Random +import System.Random.Internal + +-- $introduction +-- +-- This module provides type classes and instances for the following concepts: +-- +-- [Monadic pseudo-random number generators] 'MonadRandom' is an interface to +-- monadic pseudo-random number generators. +-- +-- [Monadic adapters] 'PureGen', 'AtomicGen', 'IOGen' and 'STGen' turn a +-- 'RandomGen' instance into a 'MonadRandom' instance. +-- +-- [Drawing from a range] 'UniformRange' is used to generate a value of a +-- datatype uniformly within an inclusive range. +-- +-- This library provides instances of 'UniformRange' for many common +-- numeric datatypes. +-- +-- [Drawing from the entire domain of a type] 'Uniform' is used to generate a +-- value of a datatype uniformly over all possible values of that datatype. +-- +-- This library provides instances of 'Uniform' for many common bounded +-- numeric datatypes. +-- +-- $usagemonadic +-- +-- In monadic code, use the relevant 'Uniform' and 'UniformRange' instances to +-- generate pseudo-random values via 'uniformM' and 'uniformRM', respectively. +-- +-- As an example, @rolls@ generates @n@ pseudo-random values of @Word8@ in the +-- range @[1, 6]@. +-- +-- > rolls :: MonadRandom g s m => Int -> g s -> m [Word8] +-- > rolls n = replicateM n . uniformR (1, 6) +-- +-- Given a /monadic/ pseudo-random number generator, you can run this +-- probabilistic computation as follows: +-- +-- >>> monadicGen <- MWC.create +-- >>> rolls 10 monadicGen :: IO [Word8] +-- [2,3,6,6,4,4,3,1,5,4] +-- +-- Given a /pure/ pseudo-random number generator, you can run it in an 'IO' or +-- 'ST' context by first applying a monadic adapter like 'AtomicGen', 'IOGen' +-- or 'STGen' and then running it with 'runGenM'. +-- +-- >>> let pureGen = mkStdGen 42 +-- >>> runGenM_ (IOGen pureGen) (rolls 10) :: IO [Word8] +-- [1,1,3,2,4,5,3,4,6,2] +-- +-- $usagepure +-- +-- In pure code, use 'runGenState' and its variants to extract the pure +-- pseudo-random value from a monadic computation based on a pure pseudo-random +-- number generator. +-- +-- >>> let pureGen = mkStdGen 42 +-- >>> runGenState_ pureGen (rolls 10) :: [Word8] +-- [1,1,3,2,4,5,3,4,6,2] + +------------------------------------------------------------------------------- +-- Pseudo-random number generator interfaces +------------------------------------------------------------------------------- + +-- $interfaces +-- +-- Pseudo-random number generators come in two flavours: /pure/ and /monadic/. +-- +-- ['System.Random.RandomGen': pure pseudo-random number generators] +-- See "System.Random" module. +-- +-- ['MonadRandom': monadic pseudo-random number generators] These generators +-- mutate their own state as they produce pseudo-random values. They +-- generally live in 'ST' or 'IO' or some transformer that implements +-- @PrimMonad@. +-- + +------------------------------------------------------------------------------- +-- Monadic adapters +------------------------------------------------------------------------------- + +-- $monadicadapters +-- +-- Pure pseudo-random number generators can be used in monadic code via the +-- adapters 'PureGen', 'AtomicGen', 'IOGen' and 'STGen'. +-- +-- * 'PureGen' can be used in any state monad. With strict 'StateT' there is +-- no performance overhead compared to using the 'RandomGen' instance +-- directly. 'PureGen' is /not/ safe to use in the presence of exceptions +-- and concurrency. +-- +-- * 'AtomicGen' is safe in the presence of exceptions and concurrency since +-- it performs all actions atomically. +-- +-- * 'IOGen' is a wrapper around an 'IORef' that holds a pure generator. +-- 'IOGen' is safe in the presence of exceptions, but not concurrency. +-- +-- * 'STGen' is a wrapper around an 'STRef' that holds a pure generator. +-- 'STGen' is safe in the presence of exceptions, but not concurrency. + +-- | Interface to operations on 'RandomGen' wrappers like 'IOGen' and 'PureGen'. +-- +-- @since 1.2 +class (RandomGen r, MonadRandom (g r) s m) => RandomGenM g r s m where + applyRandomGenM :: (r -> (a, r)) -> g r s -> m a + +-- | Splits a pseudo-random number generator into two. Overwrites the mutable +-- wrapper with one of the resulting generators and returns the other. +-- +-- @since 1.2 +splitRandomGenM :: RandomGenM g r s m => g r s -> m r +splitRandomGenM = applyRandomGenM split + +instance (RandomGen r, MonadIO m) => RandomGenM IOGen r RealWorld m where + applyRandomGenM = applyIOGen + +instance (RandomGen r, MonadIO m) => RandomGenM AtomicGen r RealWorld m where + applyRandomGenM = applyAtomicGen + +instance (RandomGen r, MonadState r m) => RandomGenM PureGen r r m where + applyRandomGenM f _ = state f + +instance RandomGen r => RandomGenM STGen r s (ST s) where + applyRandomGenM = applySTGen + +-- | Runs a mutable pseudo-random number generator from its 'Frozen' state. +-- +-- >>> import Data.Int (Int8) +-- >>> runGenM (IOGen (mkStdGen 217)) (`uniformListM` 5) :: IO ([Int8], Frozen (IOGen StdGen)) +-- ([-74,37,-50,-2,3],IOGen {unIOGen = SMGen 4273268533320920145 15251669095119325999}) +-- +-- @since 1.2 +runGenM :: MonadRandom g s m => Frozen g -> (g s -> m a) -> m (a, Frozen g) +runGenM fg action = do + g <- thawGen fg + res <- action g + fg' <- freezeGen g + pure (res, fg') + +-- | Same as 'runGenM', but only returns the generated value. +-- +-- @since 1.2 +runGenM_ :: MonadRandom g s m => Frozen g -> (g s -> m a) -> m a +runGenM_ fg action = fst <$> runGenM fg action + +-- | Generates a list of pseudo-random values. +-- +-- @since 1.2 +uniformListM :: (MonadRandom g s m, Uniform a) => g s -> Int -> m [a] +uniformListM gen n = replicateM n (uniformM gen) + +-- | Generates a pseudo-random value in a state monad. +-- +-- @since 1.2 +genRandom :: (RandomGen g, Random a, MonadState g m) => PureGen g g -> m a +genRandom _ = state random + +-- | This is a wrapper around pure generator that can be used in a monadic +-- environment. It is safe in presence of exceptions and concurrency since all +-- operations are performed atomically. +-- +-- @since 1.2 +newtype AtomicGen g s = AtomicGenI (IORef g) + +instance (RandomGen g, MonadIO m) => MonadRandom (AtomicGen g) RealWorld m where + newtype Frozen (AtomicGen g) = AtomicGen { unAtomicGen :: g } + deriving (Eq, Show, Read) + thawGen (AtomicGen g) = fmap AtomicGenI (liftIO $ newIORef g) + freezeGen (AtomicGenI gVar) = fmap AtomicGen (liftIO $ readIORef gVar) + uniformWord32R r = applyAtomicGen (genWord32R r) + {-# INLINE uniformWord32R #-} + uniformWord64R r = applyAtomicGen (genWord64R r) + {-# INLINE uniformWord64R #-} + uniformWord8 = applyAtomicGen genWord8 + {-# INLINE uniformWord8 #-} + uniformWord16 = applyAtomicGen genWord16 + {-# INLINE uniformWord16 #-} + uniformWord32 = applyAtomicGen genWord32 + {-# INLINE uniformWord32 #-} + uniformWord64 = applyAtomicGen genWord64 + {-# INLINE uniformWord64 #-} + uniformShortByteString n = applyAtomicGen (genShortByteString n) + +-- | Atomically applies a pure operation to the wrapped pseudo-random number +-- generator. +-- +-- @since 1.2 +applyAtomicGen :: MonadIO m => (g -> (a, g)) -> AtomicGen g RealWorld -> m a +applyAtomicGen op (AtomicGenI gVar) = + liftIO $ atomicModifyIORef' gVar $ \g -> + case op g of + (a, g') -> (g', a) +{-# INLINE applyAtomicGen #-} + +-- | This is a wrapper around an @IORef@ that holds a pure generator. Because of +-- extra pointer indirection it will be slightly slower than if `PureGen` is +-- being used, but faster than `AtomicGen` wrapper, since atomic modification is +-- not being used with `IOGen`. Which also means that it is not safe in a +-- concurrent setting. +-- +-- Both `IOGen` and `AtomicGen` are necessary when generation of pseudo-random +-- values happens in `IO` and especially when dealing with exception handling +-- and resource allocation, which is where `StateT` should never be used. For +-- example writing a pseudo-random number of bytes into a temporary file: +-- +-- >>> import UnliftIO.Temporary (withSystemTempFile) +-- >>> import Data.ByteString (hPutStr) +-- >>> let ioGen g = withSystemTempFile "foo.bin" $ \_ h -> uniformRM (0, 100) g >>= flip uniformByteString g >>= hPutStr h +-- +-- and then run it: +-- +-- >>> runGenM_ (IOGen (mkStdGen 1729)) ioGen +-- +-- @since 1.2 +newtype IOGen g s = IOGenI (IORef g) + +instance (RandomGen g, MonadIO m) => MonadRandom (IOGen g) RealWorld m where + newtype Frozen (IOGen g) = IOGen { unIOGen :: g } + deriving (Eq, Show, Read) + thawGen (IOGen g) = fmap IOGenI (liftIO $ newIORef g) + freezeGen (IOGenI gVar) = fmap IOGen (liftIO $ readIORef gVar) + uniformWord32R r = applyIOGen (genWord32R r) + {-# INLINE uniformWord32R #-} + uniformWord64R r = applyIOGen (genWord64R r) + {-# INLINE uniformWord64R #-} + uniformWord8 = applyIOGen genWord8 + {-# INLINE uniformWord8 #-} + uniformWord16 = applyIOGen genWord16 + {-# INLINE uniformWord16 #-} + uniformWord32 = applyIOGen genWord32 + {-# INLINE uniformWord32 #-} + uniformWord64 = applyIOGen genWord64 + {-# INLINE uniformWord64 #-} + uniformShortByteString n = applyIOGen (genShortByteString n) + +-- | Applies a pure operation to the wrapped pseudo-random number generator. +-- +-- @since 1.2 +applyIOGen :: MonadIO m => (g -> (a, g)) -> IOGen g RealWorld -> m a +applyIOGen f (IOGenI ref) = liftIO $ do + g <- readIORef ref + case f g of + (!a, !g') -> a <$ writeIORef ref g' +{-# INLINE applyIOGen #-} + + +-- | This is a wrapper wround an @STRef@ that holds a pure generator. Because of +-- extra pointer indirection it will be slightly slower than if `PureGen` is +-- being used. +-- +-- @since 1.2 +newtype STGen g s = STGenI (STRef s g) + +instance RandomGen g => MonadRandom (STGen g) s (ST s) where + newtype Frozen (STGen g) = STGen { unSTGen :: g } + deriving (Eq, Show, Read) + thawGen (STGen g) = fmap STGenI (newSTRef g) + freezeGen (STGenI gVar) = fmap STGen (readSTRef gVar) + uniformWord32R r = applySTGen (genWord32R r) + {-# INLINE uniformWord32R #-} + uniformWord64R r = applySTGen (genWord64R r) + {-# INLINE uniformWord64R #-} + uniformWord8 = applySTGen genWord8 + {-# INLINE uniformWord8 #-} + uniformWord16 = applySTGen genWord16 + {-# INLINE uniformWord16 #-} + uniformWord32 = applySTGen genWord32 + {-# INLINE uniformWord32 #-} + uniformWord64 = applySTGen genWord64 + {-# INLINE uniformWord64 #-} + uniformShortByteString n = applySTGen (genShortByteString n) + +-- | Applies a pure operation to the wrapped pseudo-random number generator. +-- +-- @since 1.2 +applySTGen :: (g -> (a, g)) -> STGen g s -> ST s a +applySTGen f (STGenI ref) = do + g <- readSTRef ref + case f g of + (!a, !g') -> a <$ writeSTRef ref g' +{-# INLINE applySTGen #-} + +-- | Runs a monadic generating action in the `ST` monad using a pure +-- pseudo-random number generator. +-- +-- @since 1.2 +runSTGen :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> (a, g) +runSTGen g action = unSTGen <$> runST (runGenM (STGen g) action) + +-- | Runs a monadic generating action in the `ST` monad using a pure +-- pseudo-random number generator. Returns only the resulting pseudo-random +-- value. +-- +-- @since 1.2 +runSTGen_ :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> a +runSTGen_ g action = fst $ runSTGen g action + + +-- $uniform +-- This library provides two type classes to generate pseudo-random values: +-- +-- * 'UniformRange' is used to generate a value of a datatype uniformly +-- within an inclusive range. +-- * 'Uniform' is used to generate a value of a datatype uniformly over all +-- possible values of that datatype. +-- +-- Types may have instances for both or just one of 'UniformRange' and +-- 'Uniform'. A few examples illustrate this: +-- +-- * 'Int', 'Word16' and 'Bool' are instances of both 'UniformRange' and +-- 'Uniform'. +-- * 'Integer', 'Float' and 'Double' each have an instance for 'UniformRange' +-- but no 'Uniform' instance. +-- * A hypothetical type @Radian@ representing angles by taking values in the +-- range @[0, 2π)@ has a trivial 'Uniform' instance, but no 'UniformRange' +-- instance: the problem is that two given @Radian@ values always span /two/ +-- ranges, one clockwise and one anti-clockwise. +-- * It is trivial to construct a @Uniform (a, b)@ instance given +-- @Uniform a@ and @Uniform b@ (and this library provides this tuple +-- instance). +-- * On the other hand, there is no correct way to construct a +-- @UniformRange (a, b)@ instance based on just @UniformRange a@ and +-- @UniformRange b@. + + +------------------------------------------------------------------------------- +-- Notes +------------------------------------------------------------------------------- + +-- $implementmonadrandom +-- +-- Typically, a monadic pseudo-random number generator has facilities to save +-- and restore its internal state in addition to generating pseudo-random +-- pseudo-random numbers. +-- +-- Here is an example instance for the monadic pseudo-random number generator +-- from the @mwc-random@ package: +-- +-- > instance (s ~ PrimState m, PrimMonad m) => MonadRandom MWC.Gen s m where +-- > newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } +-- > thawGen = fmap MWC.restore unFrozen +-- > freezeGen = fmap Frozen . MWC.save +-- > uniformWord8 = MWC.uniform +-- > uniformWord16 = MWC.uniform +-- > uniformWord32 = MWC.uniform +-- > uniformWord64 = MWC.uniform +-- > uniformShortByteString n g = unsafeSTToPrim (genShortByteStringST n (MWC.uniform g)) +-- +-- $references +-- +-- 1. Guy L. Steele, Jr., Doug Lea, and Christine H. Flood. 2014. Fast +-- splittable pseudorandom number generators. In Proceedings of the 2014 ACM +-- International Conference on Object Oriented Programming Systems Languages & +-- Applications (OOPSLA '14). ACM, New York, NY, USA, 453-472. DOI: +-- + +-- $setup +-- >>> import Control.Arrow (first, second) +-- >>> import Control.Monad (replicateM) +-- >>> import Control.Monad.Primitive +-- >>> import Data.Bits +-- >>> import Data.Int (Int32) +-- >>> import Data.Word (Word8, Word16, Word32, Word64) +-- >>> import System.IO (IOMode(WriteMode), withBinaryFile) +-- >>> import System.Random.Monad +-- >>> import qualified System.Random.MWC as MWC +-- +-- >>> :set -XFlexibleContexts +-- >>> :set -XFlexibleInstances +-- >>> :set -XMultiParamTypeClasses +-- >>> :set -XTypeFamilies +-- >>> :set -XUndecidableInstances +-- +-- >>> :set -fno-warn-missing-methods +-- +-- >>> :{ +-- instance (s ~ PrimState m, PrimMonad m) => MonadRandom MWC.Gen s m where +-- newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } +-- thawGen = fmap MWC.restore unFrozen +-- freezeGen = fmap Frozen . MWC.save +-- uniformWord8 = MWC.uniform +-- uniformWord16 = MWC.uniform +-- uniformWord32 = MWC.uniform +-- uniformWord64 = MWC.uniform +-- uniformShortByteString n g = unsafeSTToPrim (genShortByteStringST n (MWC.uniform g)) +-- :} +-- +-- >>> :{ +-- let rolls :: MonadRandom g s m => Int -> g s -> m [Word8] +-- rolls n = replicateM n . uniformRM (1, 6) +-- :} diff --git a/random.cabal b/random.cabal index de0565bed..fdf70d76b 100644 --- a/random.cabal +++ b/random.cabal @@ -29,6 +29,8 @@ custom-setup library exposed-modules: System.Random + , System.Random.Internal + , System.Random.Monad c-sources: cbits/CastFloatWord.cmm default-language: Haskell2010 ghc-options: -Wall diff --git a/tests/Spec/Range.hs b/tests/Spec/Range.hs index 8a3c40ffc..a48ac2b4a 100644 --- a/tests/Spec/Range.hs +++ b/tests/Spec/Range.hs @@ -6,7 +6,7 @@ module Spec.Range , uniformRangeWithinExcluded ) where -import System.Random +import System.Random.Monad symmetric :: (RandomGen g, Random a, Eq a) => g -> (a, a) -> Bool symmetric g (l, r) = fst (randomR (l, r) g) == fst (randomR (r, l) g) diff --git a/tests/Spec/Run.hs b/tests/Spec/Run.hs index 24f48fba1..b0e6565d5 100644 --- a/tests/Spec/Run.hs +++ b/tests/Spec/Run.hs @@ -1,7 +1,7 @@ module Spec.Run (runsEqual) where import Data.Word (Word64) -import System.Random +import System.Random.Monad runsEqual :: RandomGen g => g -> IO Bool runsEqual g = do From 1b189826ca84fc579a634c7df907c6ede17c2eaa Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Fri, 17 Apr 2020 23:00:24 +0300 Subject: [PATCH 135/170] Remove duplication of rolls and fixup haddock --- System/Random.hs | 21 ++------------------- System/Random/Monad.hs | 20 +++++++------------- 2 files changed, 9 insertions(+), 32 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index bc6738bb0..0ecd2f956 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -329,6 +329,7 @@ getStdRandom f = atomicModifyIORef' theStdGen (swap . f) -- >>> :{ -- instance RandomGen PCGen where -- genWord32 = stepGen +-- split _ = error "PCG is not splittable" -- :} -- -- @@ -369,27 +370,9 @@ getStdRandom f = atomicModifyIORef' theStdGen (swap . f) -- instance RandomGen LegacyGen where -- next = legacyNext -- genRange _ = (1, 2147483562) +-- split _ = error "Not implemented" -- :} -- --- $implementmonadrandom --- --- Typically, a monadic pseudo-random number generator has facilities to save --- and restore its internal state in addition to generating pseudo-random --- pseudo-random numbers. --- --- Here is an example instance for the monadic pseudo-random number generator --- from the @mwc-random@ package: --- --- > instance (s ~ PrimState m, PrimMonad m) => MonadRandom MWC.Gen s m where --- > newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } --- > thawGen = fmap MWC.restore unFrozen --- > freezeGen = fmap Frozen . MWC.save --- > uniformWord8 = MWC.uniform --- > uniformWord16 = MWC.uniform --- > uniformWord32 = MWC.uniform --- > uniformWord64 = MWC.uniform --- > uniformShortByteString n g = unsafeSTToPrim (genShortByteStringST n (MWC.uniform g)) --- -- $deprecations -- -- Version 1.2 mostly maintains backwards compatibility with version 1.1. This diff --git a/System/Random/Monad.hs b/System/Random/Monad.hs index 7e9a08925..b0b0cd882 100644 --- a/System/Random/Monad.hs +++ b/System/Random/Monad.hs @@ -22,12 +22,8 @@ module System.Random.Monad -- $introduction -- * Usage - -- ** How to generate pseudo-random values in monadic code -- $usagemonadic - -- ** How to generate pseudo-random values in pure code - -- $usagepure - -- * Pure and monadic pseudo-random number generator interfaces -- $interfaces , MonadRandom(..) @@ -113,14 +109,18 @@ import System.Random.Internal -- -- $usagemonadic -- +-- == How to generate pseudo-random values in monadic code +-- -- In monadic code, use the relevant 'Uniform' and 'UniformRange' instances to -- generate pseudo-random values via 'uniformM' and 'uniformRM', respectively. -- -- As an example, @rolls@ generates @n@ pseudo-random values of @Word8@ in the -- range @[1, 6]@. -- --- > rolls :: MonadRandom g s m => Int -> g s -> m [Word8] --- > rolls n = replicateM n . uniformR (1, 6) +-- >>> :{ +-- let rolls :: MonadRandom g s m => Int -> g s -> m [Word8] +-- rolls n = replicateM n . uniformRM (1, 6) +-- :} -- -- Given a /monadic/ pseudo-random number generator, you can run this -- probabilistic computation as follows: @@ -137,7 +137,7 @@ import System.Random.Internal -- >>> runGenM_ (IOGen pureGen) (rolls 10) :: IO [Word8] -- [1,1,3,2,4,5,3,4,6,2] -- --- $usagepure +-- == How to generate pseudo-random values in pure code -- -- In pure code, use 'runGenState' and its variants to extract the pure -- pseudo-random value from a monadic computation based on a pure pseudo-random @@ -460,8 +460,6 @@ runSTGen_ g action = fst $ runSTGen g action -- >>> :set -XTypeFamilies -- >>> :set -XUndecidableInstances -- --- >>> :set -fno-warn-missing-methods --- -- >>> :{ -- instance (s ~ PrimState m, PrimMonad m) => MonadRandom MWC.Gen s m where -- newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } @@ -474,7 +472,3 @@ runSTGen_ g action = fst $ runSTGen g action -- uniformShortByteString n g = unsafeSTToPrim (genShortByteStringST n (MWC.uniform g)) -- :} -- --- >>> :{ --- let rolls :: MonadRandom g s m => Int -> g s -> m [Word8] --- rolls n = replicateM n . uniformRM (1, 6) --- :} From 19c08fd03ce31ef8248a8828cff943a22755cd30 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Wed, 22 Apr 2020 13:45:13 +0200 Subject: [PATCH 136/170] Move tests - legacy tests now live in /test-legacy - new tests live in /test --- random.cabal | 26 ++++++++++--------- test-legacy/Legacy.hs | 15 +++++++++++ {tests/Legacy => test-legacy}/Random1283.hs | 2 +- {tests/Legacy => test-legacy}/RangeTest.hs | 2 +- {tests/Legacy => test-legacy}/T7936.hs | 2 +- .../Legacy => test-legacy}/TestRandomIOs.hs | 2 +- {tests/Legacy => test-legacy}/TestRandomRs.hs | 2 +- {tests => test}/Spec.hs | 0 {tests => test}/Spec/Range.hs | 0 {tests => test}/Spec/Run.hs | 0 {tests => test}/doctests.hs | 0 tests/Legacy.hs | 17 ------------ 12 files changed, 34 insertions(+), 34 deletions(-) create mode 100644 test-legacy/Legacy.hs rename {tests/Legacy => test-legacy}/Random1283.hs (97%) rename {tests/Legacy => test-legacy}/RangeTest.hs (99%) rename {tests/Legacy => test-legacy}/T7936.hs (92%) rename {tests/Legacy => test-legacy}/TestRandomIOs.hs (94%) rename {tests/Legacy => test-legacy}/TestRandomRs.hs (95%) rename {tests => test}/Spec.hs (100%) rename {tests => test}/Spec/Range.hs (100%) rename {tests => test}/Spec/Run.hs (100%) rename {tests => test}/doctests.hs (100%) delete mode 100644 tests/Legacy.hs diff --git a/random.cabal b/random.cabal index fdf70d76b..b9b3eb8a3 100644 --- a/random.cabal +++ b/random.cabal @@ -28,9 +28,11 @@ custom-setup cabal-doctest >=1.0.6 && <1.1 library - exposed-modules: System.Random - , System.Random.Internal - , System.Random.Monad + exposed-modules: + System.Random + System.Random.Internal + System.Random.Monad + c-sources: cbits/CastFloatWord.cmm default-language: Haskell2010 ghc-options: -Wall @@ -40,16 +42,16 @@ library mtl >=2.2 && <2.3, splitmix >=0.0.3 && <0.1 -test-suite legacy +test-suite legacy-test type: exitcode-stdio-1.0 main-is: Legacy.hs - hs-source-dirs: tests + hs-source-dirs: test-legacy other-modules: - Legacy.T7936 - Legacy.TestRandomIOs - Legacy.TestRandomRs - Legacy.Random1283 - Legacy.RangeTest + T7936 + TestRandomIOs + TestRandomRs + Random1283 + RangeTest default-language: Haskell2010 ghc-options: -with-rtsopts=-M4M -Wno-deprecations @@ -61,7 +63,7 @@ test-suite legacy test-suite doctests type: exitcode-stdio-1.0 main-is: doctests.hs - hs-source-dirs: tests + hs-source-dirs: test default-language: Haskell2010 build-depends: base >=4.10 && <5, @@ -74,7 +76,7 @@ test-suite doctests test-suite spec type: exitcode-stdio-1.0 main-is: Spec.hs - hs-source-dirs: tests + hs-source-dirs: test other-modules: Spec.Range Spec.Run diff --git a/test-legacy/Legacy.hs b/test-legacy/Legacy.hs new file mode 100644 index 000000000..f4660fdcd --- /dev/null +++ b/test-legacy/Legacy.hs @@ -0,0 +1,15 @@ +module Main (main) where + +import qualified Random1283 as Random1283 +import qualified RangeTest as RangeTest +import qualified T7936 as T7936 +import qualified TestRandomIOs as TestRandomIOs +import qualified TestRandomRs as TestRandomRs + +main :: IO () +main = do + Random1283.main + RangeTest.main + T7936.main + TestRandomIOs.main + TestRandomRs.main diff --git a/tests/Legacy/Random1283.hs b/test-legacy/Random1283.hs similarity index 97% rename from tests/Legacy/Random1283.hs rename to test-legacy/Random1283.hs index 53505b24b..239e29d11 100644 --- a/tests/Legacy/Random1283.hs +++ b/test-legacy/Random1283.hs @@ -1,4 +1,4 @@ -module Legacy.Random1283 (main) where +module Random1283 (main) where import Control.Concurrent import Control.Monad diff --git a/tests/Legacy/RangeTest.hs b/test-legacy/RangeTest.hs similarity index 99% rename from tests/Legacy/RangeTest.hs rename to test-legacy/RangeTest.hs index c6b2e53af..ed51541c0 100644 --- a/tests/Legacy/RangeTest.hs +++ b/test-legacy/RangeTest.hs @@ -1,6 +1,6 @@ {-# LANGUAGE CPP #-} -module Legacy.RangeTest (main) where +module RangeTest (main) where import Control.Monad import System.Random diff --git a/tests/Legacy/T7936.hs b/test-legacy/T7936.hs similarity index 92% rename from tests/Legacy/T7936.hs rename to test-legacy/T7936.hs index f351634be..47b30d1c5 100644 --- a/tests/Legacy/T7936.hs +++ b/test-legacy/T7936.hs @@ -6,7 +6,7 @@ -- $ cabal test T7936 --test-options="+RTS -M1M -RTS" -- T7936: Heap exhausted; -module Legacy.T7936 where +module T7936 where import System.Random (newStdGen) import Control.Monad (replicateM_) diff --git a/tests/Legacy/TestRandomIOs.hs b/test-legacy/TestRandomIOs.hs similarity index 94% rename from tests/Legacy/TestRandomIOs.hs rename to test-legacy/TestRandomIOs.hs index cc81477b8..4af2ddcd5 100644 --- a/tests/Legacy/TestRandomIOs.hs +++ b/test-legacy/TestRandomIOs.hs @@ -6,7 +6,7 @@ -- $ cabal test TestRandomIOs --test-options="+RTS -M1M -RTS" -- TestRandomIOs: Heap exhausted; -module Legacy.TestRandomIOs where +module TestRandomIOs where import Control.Monad (replicateM) import System.Random (randomIO) diff --git a/tests/Legacy/TestRandomRs.hs b/test-legacy/TestRandomRs.hs similarity index 95% rename from tests/Legacy/TestRandomRs.hs rename to test-legacy/TestRandomRs.hs index a85431157..90145f219 100644 --- a/tests/Legacy/TestRandomRs.hs +++ b/test-legacy/TestRandomRs.hs @@ -10,7 +10,7 @@ -- $ cabal test TestRandomRs --test-options="+RTS -M1M -RTS" -- TestRandomRs: Heap exhausted; -module Legacy.TestRandomRs where +module TestRandomRs where import Control.Monad (liftM) import System.Random (randomRs, getStdGen) diff --git a/tests/Spec.hs b/test/Spec.hs similarity index 100% rename from tests/Spec.hs rename to test/Spec.hs diff --git a/tests/Spec/Range.hs b/test/Spec/Range.hs similarity index 100% rename from tests/Spec/Range.hs rename to test/Spec/Range.hs diff --git a/tests/Spec/Run.hs b/test/Spec/Run.hs similarity index 100% rename from tests/Spec/Run.hs rename to test/Spec/Run.hs diff --git a/tests/doctests.hs b/test/doctests.hs similarity index 100% rename from tests/doctests.hs rename to test/doctests.hs diff --git a/tests/Legacy.hs b/tests/Legacy.hs deleted file mode 100644 index 260743db2..000000000 --- a/tests/Legacy.hs +++ /dev/null @@ -1,17 +0,0 @@ -module Main (main) where - -import qualified Legacy.Random1283 as Random1283 -import qualified Legacy.RangeTest as RangeTest -import qualified Legacy.T7936 as T7936 -import qualified Legacy.TestRandomIOs as TestRandomIOs --- FIXME Implement 'instance UniformRange Integer', then uncomment this import. --- import qualified Legacy.TestRandomRs as TestRandomRs - -main :: IO () -main = do - Random1283.main - RangeTest.main - T7936.main - TestRandomIOs.main - -- FIXME Implement 'instance UniformRange Integer', then uncomment TestRandomRs. - -- TestRandomRs.main From 9468d723a96e91f048985ce31217138739cb2e4f Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Wed, 22 Apr 2020 14:09:22 +0200 Subject: [PATCH 137/170] Consistent *Gen Haddocks --- System/Random/Monad.hs | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/System/Random/Monad.hs b/System/Random/Monad.hs index b0b0cd882..9ba8ffea1 100644 --- a/System/Random/Monad.hs +++ b/System/Random/Monad.hs @@ -244,10 +244,13 @@ uniformListM gen n = replicateM n (uniformM gen) genRandom :: (RandomGen g, Random a, MonadState g m) => PureGen g g -> m a genRandom _ = state random --- | This is a wrapper around pure generator that can be used in a monadic --- environment. It is safe in presence of exceptions and concurrency since all +-- | Wraps an 'IORef' that holds a pure pseudo-random number generator. All -- operations are performed atomically. -- +-- * 'AtomicGen' is safe in the presence of exceptions and concurrency. +-- * 'AtomicGen' is the slowest of the monadic adapters due to the overhead +-- of its atomic operations. +-- -- @since 1.2 newtype AtomicGen g s = AtomicGenI (IORef g) @@ -281,16 +284,14 @@ applyAtomicGen op (AtomicGenI gVar) = (a, g') -> (g', a) {-# INLINE applyAtomicGen #-} --- | This is a wrapper around an @IORef@ that holds a pure generator. Because of --- extra pointer indirection it will be slightly slower than if `PureGen` is --- being used, but faster than `AtomicGen` wrapper, since atomic modification is --- not being used with `IOGen`. Which also means that it is not safe in a --- concurrent setting. +-- | Wraps an 'IORef' that holds a pure pseudo-random number generator. +-- +-- * 'IOGen' is safe in the presence of exceptions, but not concurrency. +-- * 'IOGen' is slower than 'PureGen' due to the extra pointer indirection. +-- * 'IOGen' is faster than 'AtomicGen' since the 'IORef' operations used by +-- 'IOGen' are not atomic. -- --- Both `IOGen` and `AtomicGen` are necessary when generation of pseudo-random --- values happens in `IO` and especially when dealing with exception handling --- and resource allocation, which is where `StateT` should never be used. For --- example writing a pseudo-random number of bytes into a temporary file: +-- An example use case is writing pseudo-random bytes into a file: -- -- >>> import UnliftIO.Temporary (withSystemTempFile) -- >>> import Data.ByteString (hPutStr) @@ -332,10 +333,10 @@ applyIOGen f (IOGenI ref) = liftIO $ do (!a, !g') -> a <$ writeIORef ref g' {-# INLINE applyIOGen #-} - --- | This is a wrapper wround an @STRef@ that holds a pure generator. Because of --- extra pointer indirection it will be slightly slower than if `PureGen` is --- being used. +-- | Wraps an 'STRef' that holds a pure pseudo-random number generator. +-- +-- * 'STGen' is safe in the presence of exceptions, but not concurrency. +-- * 'STGen' is slower than 'PureGen' due to the extra pointer indirection. -- -- @since 1.2 newtype STGen g s = STGenI (STRef s g) From 3581a12aefb15b066c9004f995794419df76db7c Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Wed, 22 Apr 2020 14:24:18 +0200 Subject: [PATCH 138/170] Clarify docs for Random, Uniform, UniformRange --- System/Random.hs | 22 ++++++++++----------- System/Random/Internal.hs | 40 ++++++++++++++++++++++++++------------- System/Random/Monad.hs | 17 +++++++++-------- 3 files changed, 46 insertions(+), 33 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 0ecd2f956..c92e10f24 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -117,14 +117,12 @@ genByteString :: RandomGen g => Int -> g -> (ByteString, g) genByteString n g = runPureGenST g (uniformByteString n) {-# INLINE genByteString #-} - -{- | -With a source of pseudo-random number supply in hand, the 'Random' class allows -the programmer to extract pseudo-random values of a variety of types. - -Minimal complete definition: 'randomR' and 'random'. - --} +-- | The class of types for which uniformly distributed values can be +-- generated. +-- +-- 'Random' exists primarily for backwards compatibility with version 1.1 of +-- this library. In new code, use the better specified 'Uniform' and +-- 'UniformRange' instead. {-# DEPRECATED randomRIO "In favor of `uniformRM`" #-} {-# DEPRECATED randomIO "In favor of `uniformRM`" #-} class Random a where @@ -386,10 +384,10 @@ getStdRandom f = atomicModifyIORef' theStdGen (swap . f) -- implement word-based methods instead. See below for more information -- about how to write a 'RandomGen' instance. -- --- * This library provides instances for 'Random' for some unbounded datatypes --- for backwards compatibility. For an unbounded datatype, there is no way +-- * This library provides instances for 'Random' for some unbounded types +-- for backwards compatibility. For an unbounded type, there is no way -- to generate a value with uniform probability out of its entire domain, so --- the 'random' implementation for unbounded datatypes actually generates a +-- the 'random' implementation for unbounded types actually generates a -- value based on some fixed range. -- -- For 'Integer', 'random' generates a value in the 'Int' range. For 'Float' @@ -397,7 +395,7 @@ getStdRandom f = atomicModifyIORef' theStdGen (swap . f) -- 1)@. -- -- This library does not provide 'Uniform' instances for any unbounded --- datatypes. +-- types. -- -- $reproducibility -- diff --git a/System/Random/Internal.hs b/System/Random/Internal.hs index d477c9489..f8acc57f5 100644 --- a/System/Random/Internal.hs +++ b/System/Random/Internal.hs @@ -420,27 +420,41 @@ instance RandomGen SM32.SMGen where mkStdGen :: Int -> StdGen mkStdGen s = SM.mkSMGen $ fromIntegral s --- | Generates a value uniformly distributed over all possible values of that --- datatype. +-- | The class of types for which a uniformly distributed value can be drawn +-- from all possible values of the type. -- -- @since 1.2 class Uniform a where + -- | Generates a value uniformly distributed over all possible values of that + -- type. + -- + -- @since 1.2 uniformM :: MonadRandom g s m => g s -> m a --- | Generates a value uniformly distributed over the provided inclusive range. --- --- For example, @uniformR (1,4)@ should generate values uniformly from the set --- @[1,2,3,4]@. --- --- The API uses an inclusive range so any range can be expressed, even when --- using fixed-size ints, enumerations etc. --- --- The following law should hold to make the function always defined: --- --- > uniformRM (a,b) = uniformM (b,a) +-- | The class of types for which a uniformly distributed value can be drawn +-- from a range. -- -- @since 1.2 class UniformRange a where + -- | Generates a value uniformly distributed over the provided range. + -- + -- * For /integral types/, the range is interpreted as inclusive in the + -- lower and upper bound. + -- + -- As an example, @uniformR (1 :: Int, 4 :: Int)@ should generate values + -- uniformly from the set \(\{1,2,3,4\}\). + -- + -- * For /non-integral types/, the range is interpreted as inclusive in the + -- lower bound and exclusive in the upper bound. + -- + -- As an example, @uniformR (1 :: Float, 4 :: Float)@ should generate + -- values uniformly from the set \(\{x\;|\;1 \le x \lt 4\}\). + -- + -- The following law should hold to make the function always defined: + -- + -- > uniformRM (a, b) = uniformRM (b, a) + -- + -- @since 1.2 uniformRM :: MonadRandom g s m => (a, a) -> g s -> m a instance UniformRange Integer where diff --git a/System/Random/Monad.hs b/System/Random/Monad.hs index b0b0cd882..2dc1fee36 100644 --- a/System/Random/Monad.hs +++ b/System/Random/Monad.hs @@ -96,16 +96,16 @@ import System.Random.Internal -- 'RandomGen' instance into a 'MonadRandom' instance. -- -- [Drawing from a range] 'UniformRange' is used to generate a value of a --- datatype uniformly within an inclusive range. +-- type uniformly within a range. -- -- This library provides instances of 'UniformRange' for many common --- numeric datatypes. +-- numeric types. -- -- [Drawing from the entire domain of a type] 'Uniform' is used to generate a --- value of a datatype uniformly over all possible values of that datatype. +-- value of a type uniformly over all possible values of that type. -- -- This library provides instances of 'Uniform' for many common bounded --- numeric datatypes. +-- numeric types. -- -- $usagemonadic -- @@ -386,12 +386,13 @@ runSTGen_ g action = fst $ runSTGen g action -- $uniform +-- -- This library provides two type classes to generate pseudo-random values: -- --- * 'UniformRange' is used to generate a value of a datatype uniformly --- within an inclusive range. --- * 'Uniform' is used to generate a value of a datatype uniformly over all --- possible values of that datatype. +-- * 'UniformRange' is used to generate a value of a type uniformly within a +-- range. +-- * 'Uniform' is used to generate a value of a type uniformly over all +-- possible values of that type. -- -- Types may have instances for both or just one of 'UniformRange' and -- 'Uniform'. A few examples illustrate this: From 9cb76bc9f7dfc9974814f11b200df57cbfe83a23 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Wed, 22 Apr 2020 17:15:02 +0200 Subject: [PATCH 139/170] Add basic pure benchmarks --- bench/Main.hs | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++ random.cabal | 19 +++++++++-- 2 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 bench/Main.hs diff --git a/bench/Main.hs b/bench/Main.hs new file mode 100644 index 000000000..6bfd4a454 --- /dev/null +++ b/bench/Main.hs @@ -0,0 +1,95 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE ExplicitForAll #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} + +module Main (main) where + +import Data.Int +import Data.Proxy +import Data.Typeable +import Data.Word +import Foreign.C.Types +import Gauge.Main +import System.Random.SplitMix as SM + +import System.Random + +main :: IO () +main = do + let !sz = 1048576 + defaultMain + [ bgroup "baseline" + [ let !stdGen = mkStdGen 1337 in bench "nextWord32" $ nf (genMany SM.nextWord32 stdGen) sz + , let !stdGen = mkStdGen 1337 in bench "nextWord64" $ nf (genMany SM.nextWord64 stdGen) sz + , let !stdGen = mkStdGen 1337 in bench "nextInt" $ nf (genMany SM.nextInt stdGen) sz + , let !stdGen = mkStdGen 1337 in bench "split" $ nf (genMany SM.splitSMGen stdGen) sz + ] + , bgroup "pure" + [ bgroup "random" + [ pureRandomBench @Float sz + , pureRandomBench @Double sz + , pureRandomBench @Integer sz + ] + , bgroup "uniform" + [ pureUniformBench @Word8 sz + , pureUniformBench @Word16 sz + , pureUniformBench @Word32 sz + , pureUniformBench @Word64 sz + , pureUniformBench @Word sz + , pureUniformBench @Int8 sz + , pureUniformBench @Int16 sz + , pureUniformBench @Int32 sz + , pureUniformBench @Int64 sz + , pureUniformBench @Int sz + , pureUniformBench @Char sz + , pureUniformBench @Bool sz + , pureUniformBench @CBool sz + , pureUniformBench @CChar sz + , pureUniformBench @CSChar sz + , pureUniformBench @CUChar sz + , pureUniformBench @CShort sz + , pureUniformBench @CUShort sz + , pureUniformBench @CInt sz + , pureUniformBench @CUInt sz + , pureUniformBench @CLong sz + , pureUniformBench @CULong sz + , pureUniformBench @CPtrdiff sz + , pureUniformBench @CSize sz + , pureUniformBench @CWchar sz + , pureUniformBench @CSigAtomic sz + , pureUniformBench @CLLong sz + , pureUniformBench @CULLong sz + , pureUniformBench @CIntPtr sz + , pureUniformBench @CUIntPtr sz + , pureUniformBench @CIntMax sz + , pureUniformBench @CUIntMax sz + ] + ] + ] + +pureRandomBench :: forall a. (Typeable a, Random a) => Int -> Benchmark +pureRandomBench = let !stdGen = mkStdGen 1337 in pureBench @a (genManyRandom @a stdGen) + +pureUniformBench :: forall a. (Typeable a, Uniform a) => Int -> Benchmark +pureUniformBench = let !stdGen = mkStdGen 1337 in pureBench @a (genManyUniform @a stdGen) + +pureBench :: forall a. (Typeable a) => (Int -> ()) -> Int -> Benchmark +pureBench f sz = bench (showsTypeRep (typeRep (Proxy :: Proxy a)) "") $ nf f sz + +genManyRandom :: forall a g. (Random a, RandomGen g) => g -> Int -> () +genManyRandom = genMany (random @a) + +genManyUniform :: forall a g. (Uniform a, RandomGen g) => g -> Int -> () +genManyUniform = genMany (uniform @g @a) + +genMany :: (g -> (a, g)) -> g -> Int -> () +genMany f g0 n = go g0 0 + where + go g i + | i < n = + case f g of + (x, g') -> x `seq` go g' (i + 1) + | otherwise = g `seq` () diff --git a/random.cabal b/random.cabal index fdf70d76b..071f4e1ef 100644 --- a/random.cabal +++ b/random.cabal @@ -28,9 +28,11 @@ custom-setup cabal-doctest >=1.0.6 && <1.1 library - exposed-modules: System.Random - , System.Random.Internal - , System.Random.Monad + exposed-modules: + System.Random + System.Random.Internal + System.Random.Monad + c-sources: cbits/CastFloatWord.cmm default-language: Haskell2010 ghc-options: -Wall @@ -102,3 +104,14 @@ benchmark legacy-bench rdtsc -any, split >=0.2 && <0.3, time >=1.8 && <1.11 + +benchmark bench + type: exitcode-stdio-1.0 + main-is: Main.hs + hs-source-dirs: bench + ghc-options: -Wall -O2 + build-depends: + base >=4.10 && <5, + gauge >=0.2.3 && <0.3, + random -any, + splitmix >=0.0.3 && <0.1 From 09f6f15a0c5ed8de27f2b41b1596a75571348485 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Wed, 22 Apr 2020 17:38:22 +0200 Subject: [PATCH 140/170] Optimise Uniform Char instance --- System/Random/Internal.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/System/Random/Internal.hs b/System/Random/Internal.hs index d477c9489..141dc7188 100644 --- a/System/Random/Internal.hs +++ b/System/Random/Internal.hs @@ -638,7 +638,7 @@ charToWord32 (C# c#) = W32# (int2Word# (ord# c#)) {-# INLINE charToWord32 #-} instance Uniform Char where - uniformM g = word32ToChar <$> unsignedBitmaskWithRejectionM uniformM (charToWord32 maxBound) g + uniformM g = word32ToChar <$> unbiasedWordMult32 (charToWord32 maxBound) g {-# INLINE uniformM #-} instance UniformRange Char where uniformRM (l, h) g = From 8ae8dc8b3ae0135f1bbb3a59c0edccb1a5e9af4a Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Tue, 21 Apr 2020 11:13:03 +0200 Subject: [PATCH 141/170] Add toplevel docs --- random.cabal | 54 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/random.cabal b/random.cabal index fdf70d76b..eb13546a9 100644 --- a/random.cabal +++ b/random.cabal @@ -5,11 +5,57 @@ license: BSD3 license-file: LICENSE maintainer: core-libraries-committee@haskell.org bug-reports: https://github.com/haskell/random/issues -synopsis: random number library +synopsis: Pseudo-random number generation description: - This package provides a basic random number generation - library, including the ability to split random number - generators. + This package provides basic pseudo-random number generation, including the + ability to split random number generators. + . + == "System.Random": pure pseudo-random number interface + . + In pure code, use 'System.Random.uniform' and 'System.Random.uniformR' from + "System.Random" to generate pseudo-random numbers with a pure pseudo-random + number generator like 'System.Random.StdGen'. + . + As an example, here is how you can simulate rolls of a six-sided die using + 'System.Random.uniformR': + . + >>> let roll = flip uniformR (1, 6) :: RandomGen g => g -> (Word8, g) + >>> let rolls = unfoldr (Just . roll) :: RandomGen g => g -> [Word8] + >>> let pureGen = mkStdGen 42 + >>> take 10 (rolls pureGen) :: [Word8] + [1,1,3,2,4,5,3,4,6,2] + . + See "System.Random" for more details. + . + == "System.Random.Monad": monadic pseudo-random number interface + . + In monadic code, use 'System.Random.Monad.uniformM' and + 'System.Random.Monad.uniformRM' from "System.Random.Monad" to generate + pseudo-random numbers with a monadic pseudo-random number generator, or + using a monadic adapter. + . + As an example, here is how you can simulate rolls of a six-sided die using + 'System.Random.Monad.uniformRM': + . + >>> let rollM = uniformRM (1, 6) :: MonadRandom g s m => g s -> m Word8 + >>> let pureGen = mkStdGen 42 + >>> runGenState_ pureGen (replicateM 10 . rollM) :: m [Word8] + [1,1,3,2,4,5,3,4,6,2] + . + The monadic adapter 'System.Random.Monad.runGenState_' is used here to lift + the pure pseudo-random number generator @pureGen@ into the + 'System.Random.Monad.MonadRandom' context. + . + The monadic interface can also be used with existing monadic pseudo-random + number generators. In this example, we use the one provided in the + package: + . + >>> let rollM = uniformRM (1, 6) :: MonadRandom g s m => g s -> m Word8 + >>> monadicGen <- MWC.create + >>> (replicateM 10 . rollM) monadicGen :: m [Word8] + [2,3,6,6,4,4,3,1,5,4] + . + See "System.Random.Monad" for more details. category: System build-type: Custom From 915f361648745393ad0a299fc846bbe75621fb51 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 23 Apr 2020 09:06:45 +0200 Subject: [PATCH 142/170] Change #iterations to 1000000 --- bench/Main.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/Main.hs b/bench/Main.hs index 6bfd4a454..ac7f6d971 100644 --- a/bench/Main.hs +++ b/bench/Main.hs @@ -19,7 +19,7 @@ import System.Random main :: IO () main = do - let !sz = 1048576 + let !sz = 1000000 defaultMain [ bgroup "baseline" [ let !stdGen = mkStdGen 1337 in bench "nextWord32" $ nf (genMany SM.nextWord32 stdGen) sz From 22eb0c79f128861b1186697cbf49608ff1268c4b Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 23 Apr 2020 10:43:31 +0200 Subject: [PATCH 143/170] Reduce number of iterations to 100000 --- bench/Main.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/Main.hs b/bench/Main.hs index ac7f6d971..9fcc5710b 100644 --- a/bench/Main.hs +++ b/bench/Main.hs @@ -19,7 +19,7 @@ import System.Random main :: IO () main = do - let !sz = 1000000 + let !sz = 100000 defaultMain [ bgroup "baseline" [ let !stdGen = mkStdGen 1337 in bench "nextWord32" $ nf (genMany SM.nextWord32 stdGen) sz From e1f224ba749795db84dca454955dd469675f5b95 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 23 Apr 2020 10:43:09 +0200 Subject: [PATCH 144/170] Add uniformR benchmarks --- bench/Main.hs | 136 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 128 insertions(+), 8 deletions(-) diff --git a/bench/Main.hs b/bench/Main.hs index 9fcc5710b..a0e45f610 100644 --- a/bench/Main.hs +++ b/bench/Main.hs @@ -67,23 +67,143 @@ main = do , pureUniformBench @CIntMax sz , pureUniformBench @CUIntMax sz ] + , bgroup "uniformR" + [ bgroup "full" + [ pureUniformRFullBench @Word8 sz + , pureUniformRFullBench @Word16 sz + , pureUniformRFullBench @Word32 sz + , pureUniformRFullBench @Word64 sz + , pureUniformRFullBench @Word sz + , pureUniformRFullBench @Int8 sz + , pureUniformRFullBench @Int16 sz + , pureUniformRFullBench @Int32 sz + , pureUniformRFullBench @Int64 sz + , pureUniformRFullBench @Int sz + , pureUniformRFullBench @Char sz + , pureUniformRFullBench @Bool sz + , pureUniformRFullBench @CBool sz + , pureUniformRFullBench @CChar sz + , pureUniformRFullBench @CSChar sz + , pureUniformRFullBench @CUChar sz + , pureUniformRFullBench @CShort sz + , pureUniformRFullBench @CUShort sz + , pureUniformRFullBench @CInt sz + , pureUniformRFullBench @CUInt sz + , pureUniformRFullBench @CLong sz + , pureUniformRFullBench @CULong sz + , pureUniformRFullBench @CPtrdiff sz + , pureUniformRFullBench @CSize sz + , pureUniformRFullBench @CWchar sz + , pureUniformRFullBench @CSigAtomic sz + , pureUniformRFullBench @CLLong sz + , pureUniformRFullBench @CULLong sz + , pureUniformRFullBench @CIntPtr sz + , pureUniformRFullBench @CUIntPtr sz + , pureUniformRFullBench @CIntMax sz + , pureUniformRFullBench @CUIntMax sz + ] + , bgroup "excludeMax" + [ pureUniformRExcludeMaxBench @Word8 sz + , pureUniformRExcludeMaxBench @Word16 sz + , pureUniformRExcludeMaxBench @Word32 sz + , pureUniformRExcludeMaxBench @Word64 sz + , pureUniformRExcludeMaxBench @Word sz + , pureUniformRExcludeMaxBench @Int8 sz + , pureUniformRExcludeMaxBench @Int16 sz + , pureUniformRExcludeMaxBench @Int32 sz + , pureUniformRExcludeMaxBench @Int64 sz + , pureUniformRExcludeMaxBench @Int sz + , pureUniformRExcludeMaxEnumBench @Char sz + , pureUniformRExcludeMaxEnumBench @Bool sz + , pureUniformRExcludeMaxBench @CBool sz + , pureUniformRExcludeMaxBench @CChar sz + , pureUniformRExcludeMaxBench @CSChar sz + , pureUniformRExcludeMaxBench @CUChar sz + , pureUniformRExcludeMaxBench @CShort sz + , pureUniformRExcludeMaxBench @CUShort sz + , pureUniformRExcludeMaxBench @CInt sz + , pureUniformRExcludeMaxBench @CUInt sz + , pureUniformRExcludeMaxBench @CLong sz + , pureUniformRExcludeMaxBench @CULong sz + , pureUniformRExcludeMaxBench @CPtrdiff sz + , pureUniformRExcludeMaxBench @CSize sz + , pureUniformRExcludeMaxBench @CWchar sz + , pureUniformRExcludeMaxBench @CSigAtomic sz + , pureUniformRExcludeMaxBench @CLLong sz + , pureUniformRExcludeMaxBench @CULLong sz + , pureUniformRExcludeMaxBench @CIntPtr sz + , pureUniformRExcludeMaxBench @CUIntPtr sz + , pureUniformRExcludeMaxBench @CIntMax sz + , pureUniformRExcludeMaxBench @CUIntMax sz + ] + , bgroup "includeHalf" + [ pureUniformRIncludeHalfBench @Word8 sz + , pureUniformRIncludeHalfBench @Word16 sz + , pureUniformRIncludeHalfBench @Word32 sz + , pureUniformRIncludeHalfBench @Word64 sz + , pureUniformRIncludeHalfBench @Word sz + , pureUniformRIncludeHalfBench @Int8 sz + , pureUniformRIncludeHalfBench @Int16 sz + , pureUniformRIncludeHalfBench @Int32 sz + , pureUniformRIncludeHalfBench @Int64 sz + , pureUniformRIncludeHalfBench @Int sz + , pureUniformRIncludeHalfEnumBench @Char sz + , pureUniformRIncludeHalfEnumBench @Bool sz + , pureUniformRIncludeHalfBench @CBool sz + , pureUniformRIncludeHalfBench @CChar sz + , pureUniformRIncludeHalfBench @CSChar sz + , pureUniformRIncludeHalfBench @CUChar sz + , pureUniformRIncludeHalfBench @CShort sz + , pureUniformRIncludeHalfBench @CUShort sz + , pureUniformRIncludeHalfBench @CInt sz + , pureUniformRIncludeHalfBench @CUInt sz + , pureUniformRIncludeHalfBench @CLong sz + , pureUniformRIncludeHalfBench @CULong sz + , pureUniformRIncludeHalfBench @CPtrdiff sz + , pureUniformRIncludeHalfBench @CSize sz + , pureUniformRIncludeHalfBench @CWchar sz + , pureUniformRIncludeHalfBench @CSigAtomic sz + , pureUniformRIncludeHalfBench @CLLong sz + , pureUniformRIncludeHalfBench @CULLong sz + , pureUniformRIncludeHalfBench @CIntPtr sz + , pureUniformRIncludeHalfBench @CUIntPtr sz + , pureUniformRIncludeHalfBench @CIntMax sz + , pureUniformRIncludeHalfBench @CUIntMax sz + ] + ] ] ] pureRandomBench :: forall a. (Typeable a, Random a) => Int -> Benchmark -pureRandomBench = let !stdGen = mkStdGen 1337 in pureBench @a (genManyRandom @a stdGen) +pureRandomBench = let !stdGen = mkStdGen 1337 in pureBench @a (genMany (random @a) stdGen) pureUniformBench :: forall a. (Typeable a, Uniform a) => Int -> Benchmark -pureUniformBench = let !stdGen = mkStdGen 1337 in pureBench @a (genManyUniform @a stdGen) +pureUniformBench = let !stdGen = mkStdGen 1337 in pureBench @a (genMany (uniform @_ @a) stdGen) -pureBench :: forall a. (Typeable a) => (Int -> ()) -> Int -> Benchmark -pureBench f sz = bench (showsTypeRep (typeRep (Proxy :: Proxy a)) "") $ nf f sz +pureUniformRFullBench :: forall a. (Typeable a, UniformRange a, Bounded a) => Int -> Benchmark +pureUniformRFullBench = let !range = (minBound @a, maxBound @a) in pureUniformRBench range + +pureUniformRExcludeMaxBench :: forall a. (Typeable a, UniformRange a, Bounded a, Num a) => Int -> Benchmark +pureUniformRExcludeMaxBench = let !range = (minBound @a, maxBound @a - 1) in pureUniformRBench range + +pureUniformRExcludeMaxEnumBench :: forall a. (Typeable a, UniformRange a, Bounded a, Enum a) => Int -> Benchmark +pureUniformRExcludeMaxEnumBench = let !range = (minBound @a, pred (maxBound @a)) in pureUniformRBench range -genManyRandom :: forall a g. (Random a, RandomGen g) => g -> Int -> () -genManyRandom = genMany (random @a) +pureUniformRIncludeHalfBench :: forall a. (Typeable a, UniformRange a, Bounded a, Integral a) => Int -> Benchmark +pureUniformRIncludeHalfBench = let !range = (minBound @a, (maxBound @a `div` 2) + 1) in pureUniformRBench range -genManyUniform :: forall a g. (Uniform a, RandomGen g) => g -> Int -> () -genManyUniform = genMany (uniform @g @a) +pureUniformRIncludeHalfEnumBench :: forall a. (Typeable a, UniformRange a, Bounded a, Enum a) => Int -> Benchmark +pureUniformRIncludeHalfEnumBench = + let !range = (succ (minBound @a), toEnum ((fromEnum (maxBound @a) `div` 2) + 1)) + in pureUniformRBench range + +pureUniformRBench :: forall a. (Typeable a, UniformRange a) => (a, a) -> Int -> Benchmark +pureUniformRBench range = + let !stdGen = mkStdGen 1337 + in pureBench @a (genMany (flip uniformR range) stdGen) + +pureBench :: forall a. (Typeable a) => (Int -> ()) -> Int -> Benchmark +pureBench f sz = bench (showsTypeRep (typeRep (Proxy :: Proxy a)) "") $ nf f sz genMany :: (g -> (a, g)) -> g -> Int -> () genMany f g0 n = go g0 0 From 990d3fc3fa062265309c4b04480dbfb7a661e140 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 23 Apr 2020 10:43:55 +0200 Subject: [PATCH 145/170] Optimise uniformR for Word8, Word16, Word32, Char --- System/Random/Internal.hs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/System/Random/Internal.hs b/System/Random/Internal.hs index 141dc7188..3e59ab06c 100644 --- a/System/Random/Internal.hs +++ b/System/Random/Internal.hs @@ -492,22 +492,21 @@ instance Uniform Word8 where uniformM = uniformWord8 instance UniformRange Word8 where {-# INLINE uniformRM #-} - uniformRM = unsignedBitmaskWithRejectionRM + uniformRM = unbiasedWordMult32RM instance Uniform Word16 where {-# INLINE uniformM #-} uniformM = uniformWord16 instance UniformRange Word16 where {-# INLINE uniformRM #-} - uniformRM = unsignedBitmaskWithRejectionRM + uniformRM = unbiasedWordMult32RM instance Uniform Word32 where {-# INLINE uniformM #-} uniformM = uniformWord32 instance UniformRange Word32 where {-# INLINE uniformRM #-} - uniformRM (b, t) g | b > t = (+t) <$> unbiasedWordMult32 (b - t) g - | otherwise = (+b) <$> unbiasedWordMult32 (t - b) g + uniformRM = unbiasedWordMult32RM instance Uniform Word64 where {-# INLINE uniformM #-} @@ -642,7 +641,7 @@ instance Uniform Char where {-# INLINE uniformM #-} instance UniformRange Char where uniformRM (l, h) g = - word32ToChar <$> unsignedBitmaskWithRejectionRM (charToWord32 l, charToWord32 h) g + word32ToChar <$> unbiasedWordMult32RM (charToWord32 l, charToWord32 h) g {-# INLINE uniformRM #-} instance Uniform Bool where @@ -804,6 +803,15 @@ uniformIntegerWords n gen = go 0 n go ((acc `shiftL` WORD_SIZE_IN_BITS) .|. (fromIntegral w)) (i - 1) {-# INLINE uniformIntegerWords #-} +-- | Uniformly generate an 'Integral' in an inclusive-inclusive range. +-- +-- Only use for integrals size less than or equal to that of 'Word32'. +unbiasedWordMult32RM :: (MonadRandom g s m, Integral a) => (a, a) -> g s -> m a +unbiasedWordMult32RM (b, t) g + | b <= t = ((+b) . fromIntegral) <$> unbiasedWordMult32 (fromIntegral (t - b)) g + | otherwise = ((+t) . fromIntegral) <$> unbiasedWordMult32 (fromIntegral (b - t)) g +{-# SPECIALIZE unbiasedWordMult32RM :: MonadRandom g s m => (Word8, Word8) -> g s -> m Word8 #-} + -- | Uniformly generate Word32 in @[0, s]@. unbiasedWordMult32 :: MonadRandom g s m => Word32 -> g s -> m Word32 unbiasedWordMult32 s g From 45d61bcb91c4e75ba99d6416206e0503fd38b699 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 23 Apr 2020 11:32:38 +0200 Subject: [PATCH 146/170] Fix doctests --- System/Random/Monad.hs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/System/Random/Monad.hs b/System/Random/Monad.hs index b0b0cd882..eeab57f48 100644 --- a/System/Random/Monad.hs +++ b/System/Random/Monad.hs @@ -126,16 +126,16 @@ import System.Random.Internal -- probabilistic computation as follows: -- -- >>> monadicGen <- MWC.create --- >>> rolls 10 monadicGen :: IO [Word8] --- [2,3,6,6,4,4,3,1,5,4] +-- >>> rolls 12 monadicGen :: IO [Word8] +-- [4,1,2,4,4,5,2,1,5,4,6,6] -- -- Given a /pure/ pseudo-random number generator, you can run it in an 'IO' or -- 'ST' context by first applying a monadic adapter like 'AtomicGen', 'IOGen' -- or 'STGen' and then running it with 'runGenM'. -- --- >>> let pureGen = mkStdGen 42 +-- >>> let pureGen = mkStdGen 41 -- >>> runGenM_ (IOGen pureGen) (rolls 10) :: IO [Word8] --- [1,1,3,2,4,5,3,4,6,2] +-- [6,4,5,1,1,3,2,4,5,5] -- -- == How to generate pseudo-random values in pure code -- @@ -143,9 +143,9 @@ import System.Random.Internal -- pseudo-random value from a monadic computation based on a pure pseudo-random -- number generator. -- --- >>> let pureGen = mkStdGen 42 +-- >>> let pureGen = mkStdGen 41 -- >>> runGenState_ pureGen (rolls 10) :: [Word8] --- [1,1,3,2,4,5,3,4,6,2] +-- [6,4,5,1,1,3,2,4,5,5] ------------------------------------------------------------------------------- -- Pseudo-random number generator interfaces From 625faddb7377c07e7e02cb77d6efae2a63c3be3c Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 23 Apr 2020 12:02:02 +0200 Subject: [PATCH 147/170] Add uniformR benchmark for Float, Double, Integer --- bench/Main.hs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bench/Main.hs b/bench/Main.hs index a0e45f610..0da1f9dd1 100644 --- a/bench/Main.hs +++ b/bench/Main.hs @@ -170,6 +170,13 @@ main = do , pureUniformRIncludeHalfBench @CIntMax sz , pureUniformRIncludeHalfBench @CUIntMax sz ] + , bgroup "unbounded" + [ pureUniformRBench @Float (1.23e-4, 5.67e8) sz + , pureUniformRBench @Double (1.23e-4, 5.67e8) sz + , let !i = (10 :: Integer) ^ (100 :: Integer) + !range = (-i - 1, i + 1) + in pureUniformRBench @Integer range sz + ] ] ] ] From bb2d26ab5300d90ad249e82eeb9e8c6d86b073f5 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Tue, 21 Apr 2020 20:55:54 +0300 Subject: [PATCH 148/170] Fix order of arguments in uniformR --- System/Random.hs | 4 ++-- bench/Main.hs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index c92e10f24..4141aa356 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -106,8 +106,8 @@ uniform g = runGenState g uniformM -- | Pure version of `uniformRM` that works with instances of `RandomGen` -- -- @since 1.2 -uniformR :: (RandomGen g, UniformRange a) => g -> (a, a) -> (a, g) -uniformR g r = runGenState g (uniformRM r) +uniformR :: (RandomGen g, UniformRange a) => (a, a) -> g -> (a, g) +uniformR r g = runGenState g (uniformRM r) -- | Generates a 'ByteString' of the specified size using a pure pseudo-random -- number generator. See 'uniformByteString' for the monadic version. diff --git a/bench/Main.hs b/bench/Main.hs index 0da1f9dd1..0f1b8eaf4 100644 --- a/bench/Main.hs +++ b/bench/Main.hs @@ -207,7 +207,7 @@ pureUniformRIncludeHalfEnumBench = pureUniformRBench :: forall a. (Typeable a, UniformRange a) => (a, a) -> Int -> Benchmark pureUniformRBench range = let !stdGen = mkStdGen 1337 - in pureBench @a (genMany (flip uniformR range) stdGen) + in pureBench @a (genMany (uniformR range) stdGen) pureBench :: forall a. (Typeable a) => (Int -> ()) -> Int -> Benchmark pureBench f sz = bench (showsTypeRep (typeRep (Proxy :: Proxy a)) "") $ nf f sz From 99990d1922c5fdc62041f926ab5d1c3f4c20da1d Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Wed, 29 Apr 2020 09:43:27 +0200 Subject: [PATCH 149/170] Add script to compare benchmarks (#114) --- scripts/bench.sh | 12 ++++++++++ scripts/compare.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100755 scripts/bench.sh create mode 100755 scripts/compare.py diff --git a/scripts/bench.sh b/scripts/bench.sh new file mode 100755 index 000000000..ec10ffc48 --- /dev/null +++ b/scripts/bench.sh @@ -0,0 +1,12 @@ +#! /usr/bin/env bash + +# Usage: bench.sh +# +# Runs random:bench and outputs the results in a CSV file with the name +# "-.csv". + +set -euo pipefail + +OUTPUT="$(git branch --show-current)-$(git rev-parse --short HEAD).csv" +stack bench random:bench --ba "--csv=${OUTPUT} --small" +echo "Results are in $OUTPUT" diff --git a/scripts/compare.py b/scripts/compare.py new file mode 100755 index 000000000..c9cd63759 --- /dev/null +++ b/scripts/compare.py @@ -0,0 +1,59 @@ +#! /usr/bin/env nix-shell +#! nix-shell -i python3 -p python3 python37Packages.pandas + +# Usage: compare.py +# +# Compares the benchmark results in the second argument with those in the first +# argument. Outputs a comparison of those rows where the results differ +# significantly. + +import pandas as pd + +# Threshold for the relative difference, as a percentage +SIGNIFICANT = 20 + +if __name__ == "__main__": + from sys import argv + + ref = pd.read_csv(argv[1]) + res = pd.read_csv(argv[2]) + + # v1.1 benchmarks call it 'randomR', newer ones 'uniformR'. They are + # directly comparable. We normalise to 'uniformR' here. + ref["Name"] = ref["Name"].str.replace("randomR", "uniformR") + res["Name"] = res["Name"].str.replace("randomR", "uniformR") + comp = pd.merge( + ref, + res, + on="Name", + how="outer", + validate="one_to_one", + suffixes=("_ref", "_res"), + ) + + diff_col = "Diff_rel" + comp[diff_col] = (comp["Mean_ref"] - comp["Mean_res"]) / comp["Mean_res"] + + cols = ["Name", "Mean_ref", "Mean_res", diff_col] + print_opts = { + "index": False, + "columns": cols, + "formatters": {diff_col: "{:,.0%}".format}, + } + significant_percent = SIGNIFICANT / 100 + + slower = comp[comp[diff_col] < -significant_percent] + if slower.empty: + print("SLOWER: none") + else: + print("SLOWER") + print(slower.to_string(**print_opts)) + + print() + + faster = comp[comp[diff_col] > significant_percent] + if faster.empty: + print("FASTER: none") + else: + print("\nFASTER") + print(faster.to_string(**print_opts)) From f913dcd9dfb3199787d627d32995441d35829fd0 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Wed, 29 Apr 2020 11:46:24 +0100 Subject: [PATCH 150/170] Check issue remains fixed --- random.cabal | 3 ++- test/Spec.hs | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/random.cabal b/random.cabal index a8350727f..28d2f5e78 100644 --- a/random.cabal +++ b/random.cabal @@ -135,7 +135,8 @@ test-suite spec smallcheck >=1.1 && <1.2, tasty >=1.0 && <1.3, tasty-smallcheck >=0.8 && <0.9, - tasty-expected-failure >=0.11 && <0.12 + tasty-expected-failure >=0.11 && <0.12, + tasty-hunit >=0.10 && <0.11 benchmark legacy-bench type: exitcode-stdio-1.0 diff --git a/test/Spec.hs b/test/Spec.hs index 800b5d1b1..1c5c613b3 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -13,6 +13,7 @@ import Data.Word import Data.Int import System.Random import Test.Tasty +import Test.Tasty.HUnit import Test.Tasty.SmallCheck as SC import Test.SmallCheck.Series as SC import Data.Typeable @@ -71,8 +72,21 @@ main = -- , bitmaskSpec @Word64 -- , bitmaskSpec @Word , runSpec + , unitTests ] +unitTests :: TestTree +unitTests = testGroup "Unit tests" + [ -- Check that https://github.com/haskell/random/issues/53 does not regress + + testCase "Subnormal generation respects upper bound" $ + [] @?= (filter (>4.0e-45) $ take 10 $ randomRs (0,(4.0e-45::Float)) $ mkStdGen 0) + + , testCase "Subnormal generation includes upper bound" $ + 1.0e-45 `elem` (take 100 $ randomRs (0,(1.0e-45::Float)) $ mkStdGen 0) @? + "Does not contain 1.0e-45" + ] + showsType :: forall t . Typeable t => ShowS showsType = showsTypeRep (typeRep (Proxy :: Proxy t)) From b72f1a5fca38d409e2a1c09f3ceb4b4334c559e8 Mon Sep 17 00:00:00 2001 From: idontgetoutmuch Date: Wed, 29 Apr 2020 12:56:47 +0100 Subject: [PATCH 151/170] Update test/Spec.hs Co-Authored-By: Leonhard Markert --- test/Spec.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Spec.hs b/test/Spec.hs index 1c5c613b3..09eadff68 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -80,7 +80,7 @@ unitTests = testGroup "Unit tests" [ -- Check that https://github.com/haskell/random/issues/53 does not regress testCase "Subnormal generation respects upper bound" $ - [] @?= (filter (>4.0e-45) $ take 10 $ randomRs (0,(4.0e-45::Float)) $ mkStdGen 0) + [] @?= (filter (>4.0e-45) $ take 100000 $ randomRs (0,(4.0e-45::Float)) $ mkStdGen 0) , testCase "Subnormal generation includes upper bound" $ 1.0e-45 `elem` (take 100 $ randomRs (0,(1.0e-45::Float)) $ mkStdGen 0) @? From 8ca0d1ebb470ecc14ed625f926804672d26bada8 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Wed, 29 Apr 2020 13:03:06 +0100 Subject: [PATCH 152/170] Respond to comments --- test/Spec.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Spec.hs b/test/Spec.hs index 09eadff68..1bd0f8a20 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -72,14 +72,14 @@ main = -- , bitmaskSpec @Word64 -- , bitmaskSpec @Word , runSpec - , unitTests + , floatTests ] -unitTests :: TestTree -unitTests = testGroup "Unit tests" +floatTests :: TestTree +floatTests = testGroup "(Float)" [ -- Check that https://github.com/haskell/random/issues/53 does not regress - testCase "Subnormal generation respects upper bound" $ + testCase "Subnormal generation not above upper bound" $ [] @?= (filter (>4.0e-45) $ take 100000 $ randomRs (0,(4.0e-45::Float)) $ mkStdGen 0) , testCase "Subnormal generation includes upper bound" $ From 7f91c2fb16a83e1cd309f1c115caef672a453b07 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Wed, 29 Apr 2020 19:16:06 +0200 Subject: [PATCH 153/170] Fix all regressions relative to v1.1 (#116) * Make bitmask-with-rejection non-recursive * INLINE some uniformRM implementations --- System/Random/Internal.hs | 44 +++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/System/Random/Internal.hs b/System/Random/Internal.hs index cb95b44c5..bb8044244 100644 --- a/System/Random/Internal.hs +++ b/System/Random/Internal.hs @@ -469,16 +469,19 @@ instance Uniform Int16 where uniformM = fmap (fromIntegral :: Word16 -> Int16) . uniformWord16 instance UniformRange Int16 where uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int16 -> Word16) fromIntegral + {-# INLINE uniformRM #-} instance Uniform Int32 where uniformM = fmap (fromIntegral :: Word32 -> Int32) . uniformWord32 instance UniformRange Int32 where uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int32 -> Word32) fromIntegral + {-# INLINE uniformRM #-} instance Uniform Int64 where uniformM = fmap (fromIntegral :: Word64 -> Int64) . uniformWord64 instance UniformRange Int64 where uniformRM = signedBitmaskWithRejectionRM (fromIntegral :: Int64 -> Word64) fromIntegral + {-# INLINE uniformRM #-} instance Uniform Int where #if WORD_SIZE_IN_BITS < 64 @@ -533,107 +536,129 @@ instance Uniform CBool where uniformM = fmap CBool . uniformM instance UniformRange CBool where uniformRM (CBool b, CBool t) = fmap CBool . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CChar where uniformM = fmap CChar . uniformM instance UniformRange CChar where uniformRM (CChar b, CChar t) = fmap CChar . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CSChar where uniformM = fmap CSChar . uniformM instance UniformRange CSChar where uniformRM (CSChar b, CSChar t) = fmap CSChar . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CUChar where uniformM = fmap CUChar . uniformM instance UniformRange CUChar where uniformRM (CUChar b, CUChar t) = fmap CUChar . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CShort where uniformM = fmap CShort . uniformM instance UniformRange CShort where uniformRM (CShort b, CShort t) = fmap CShort . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CUShort where uniformM = fmap CUShort . uniformM instance UniformRange CUShort where uniformRM (CUShort b, CUShort t) = fmap CUShort . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CInt where uniformM = fmap CInt . uniformM instance UniformRange CInt where uniformRM (CInt b, CInt t) = fmap CInt . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CUInt where uniformM = fmap CUInt . uniformM instance UniformRange CUInt where uniformRM (CUInt b, CUInt t) = fmap CUInt . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CLong where uniformM = fmap CLong . uniformM instance UniformRange CLong where uniformRM (CLong b, CLong t) = fmap CLong . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CULong where uniformM = fmap CULong . uniformM instance UniformRange CULong where uniformRM (CULong b, CULong t) = fmap CULong . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CPtrdiff where uniformM = fmap CPtrdiff . uniformM instance UniformRange CPtrdiff where uniformRM (CPtrdiff b, CPtrdiff t) = fmap CPtrdiff . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CSize where uniformM = fmap CSize . uniformM instance UniformRange CSize where uniformRM (CSize b, CSize t) = fmap CSize . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CWchar where uniformM = fmap CWchar . uniformM instance UniformRange CWchar where uniformRM (CWchar b, CWchar t) = fmap CWchar . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CSigAtomic where uniformM = fmap CSigAtomic . uniformM instance UniformRange CSigAtomic where uniformRM (CSigAtomic b, CSigAtomic t) = fmap CSigAtomic . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CLLong where uniformM = fmap CLLong . uniformM instance UniformRange CLLong where uniformRM (CLLong b, CLLong t) = fmap CLLong . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CULLong where uniformM = fmap CULLong . uniformM instance UniformRange CULLong where uniformRM (CULLong b, CULLong t) = fmap CULLong . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CIntPtr where uniformM = fmap CIntPtr . uniformM instance UniformRange CIntPtr where uniformRM (CIntPtr b, CIntPtr t) = fmap CIntPtr . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CUIntPtr where uniformM = fmap CUIntPtr . uniformM instance UniformRange CUIntPtr where uniformRM (CUIntPtr b, CUIntPtr t) = fmap CUIntPtr . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CIntMax where uniformM = fmap CIntMax . uniformM instance UniformRange CIntMax where uniformRM (CIntMax b, CIntMax t) = fmap CIntMax . uniformRM (b, t) + {-# INLINE uniformRM #-} instance Uniform CUIntMax where uniformM = fmap CUIntMax . uniformM instance UniformRange CUIntMax where uniformRM (CUIntMax b, CUIntMax t) = fmap CUIntMax . uniformRM (b, t) + {-# INLINE uniformRM #-} instance UniformRange CFloat where uniformRM (CFloat l, CFloat h) = fmap CFloat . uniformRM (l, h) + {-# INLINE uniformRM #-} instance UniformRange CDouble where uniformRM (CDouble l, CDouble h) = fmap CDouble . uniformRM (l, h) + {-# INLINE uniformRM #-} -- The `chr#` and `ord#` are the prim functions that will be called, regardless of which @@ -859,11 +884,10 @@ unsignedBitmaskWithRejectionRM :: -> g s -> m a unsignedBitmaskWithRejectionRM (bottom, top) gen - | bottom > top = unsignedBitmaskWithRejectionRM (top, bottom) gen | bottom == top = pure top - | otherwise = (bottom +) <$> unsignedBitmaskWithRejectionM uniformM range gen + | otherwise = (b +) <$> unsignedBitmaskWithRejectionM uniformM r gen where - range = top - bottom + (b, r) = if bottom > top then (top, bottom - top) else (bottom, top - bottom) {-# INLINE unsignedBitmaskWithRejectionRM #-} -- | This works for signed integrals by explicit conversion to unsigned and abusing overflow @@ -875,13 +899,15 @@ signedBitmaskWithRejectionRM :: -> g s -> f b signedBitmaskWithRejectionRM toUnsigned fromUnsigned (bottom, top) gen - | bottom > top = signedBitmaskWithRejectionRM toUnsigned fromUnsigned (top, bottom) gen | bottom == top = pure top - | otherwise = (bottom +) . fromUnsigned <$> - unsignedBitmaskWithRejectionM uniformM range gen - where - -- This works in all cases, see Appendix 1 at the end of the file. - range = toUnsigned top - toUnsigned bottom + | otherwise = + (b +) . fromUnsigned <$> unsignedBitmaskWithRejectionM uniformM r gen + -- This works in all cases, see Appendix 1 at the end of the file. + where + (b, r) = + if bottom > top + then (top, toUnsigned bottom - toUnsigned top) + else (bottom, toUnsigned top - toUnsigned bottom) {-# INLINE signedBitmaskWithRejectionRM #-} unsignedBitmaskWithRejectionM :: (Ord a, FiniteBits a, Num a, MonadRandom g s m) => (g s -> m a) -> a -> g s -> m a From adc396503ba8fc70601ac0142109958ee4a835e7 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Mon, 4 May 2020 23:09:35 +0300 Subject: [PATCH 154/170] Move `randomIO` and `randomRIO` outside of `Random` class. Switch to `MoandIO` --- System/Random.hs | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 4141aa356..be16853bf 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -33,6 +33,8 @@ module System.Random , getStdGen , setStdGen , newStdGen + , randomIO + , randomRIO -- * Compatibility and reproducibility -- ** Backwards compatibility and deprecations @@ -50,6 +52,7 @@ module System.Random ) where import Control.Arrow +import Control.Monad.IO.Class import Data.ByteString (ByteString) import Data.Int import Data.IORef @@ -123,8 +126,6 @@ genByteString n g = runPureGenST g (uniformByteString n) -- 'Random' exists primarily for backwards compatibility with version 1.1 of -- this library. In new code, use the better specified 'Uniform' and -- 'UniformRange' instead. -{-# DEPRECATED randomRIO "In favor of `uniformRM`" #-} -{-# DEPRECATED randomIO "In favor of `uniformRM`" #-} class Random a where -- | Takes a range /(lo,hi)/ and a pseudo-random number generator @@ -164,15 +165,6 @@ class Random a where randoms :: RandomGen g => g -> [a] randoms g = build (\cons _nil -> buildRandoms cons random g) - -- | A variant of 'randomR' that uses the global pseudo-random number - -- generator. - randomRIO :: (a,a) -> IO a - randomRIO range = getStdRandom (randomR range) - - -- | A variant of 'random' that uses the global pseudo-random number - -- generator. - randomIO :: IO a - randomIO = getStdRandom random -- | Produce an infinite list-equivalent of pseudo-random values. {-# INLINE buildRandoms #-} @@ -250,21 +242,21 @@ instance Random Float where -- 'IO' context. -- |Sets the global pseudo-random number generator. -setStdGen :: StdGen -> IO () -setStdGen sgen = writeIORef theStdGen sgen +setStdGen :: MonadIO m => StdGen -> m () +setStdGen = liftIO . writeIORef theStdGen -- |Gets the global pseudo-random number generator. -getStdGen :: IO StdGen -getStdGen = readIORef theStdGen +getStdGen :: MonadIO m => m StdGen +getStdGen = liftIO $ readIORef theStdGen theStdGen :: IORef StdGen -theStdGen = unsafePerformIO $ SM.initSMGen >>= newIORef +theStdGen = unsafePerformIO $ SM.initSMGen >>= newIORef {-# NOINLINE theStdGen #-} -- |Applies 'split' to the current global pseudo-random generator, -- updates it with one of the results, and returns the other. -newStdGen :: IO StdGen -newStdGen = atomicModifyIORef' theStdGen split +newStdGen :: MonadIO m => m StdGen +newStdGen = liftIO $ atomicModifyIORef' theStdGen split {- |Uses the supplied function to get a value from the current global random generator, and updates the global generator with the new generator @@ -275,9 +267,20 @@ between 1 and 6: > rollDice = getStdRandom (randomR (1,6)) -} -getStdRandom :: (StdGen -> (a,StdGen)) -> IO a -getStdRandom f = atomicModifyIORef' theStdGen (swap . f) - where swap (v,g) = (g,v) +getStdRandom :: MonadIO m => (StdGen -> (a, StdGen)) -> m a +getStdRandom f = liftIO $ atomicModifyIORef' theStdGen (swap . f) + where swap (v, g) = (g, v) + + +-- | A variant of 'randomR' that uses the global pseudo-random number +-- generator. +randomRIO :: (Random a, MonadIO m) => (a, a) -> m a +randomRIO range = liftIO $ getStdRandom (randomR range) + +-- | A variant of 'random' that uses the global pseudo-random number +-- generator. +randomIO :: (Random a, MonadIO m) => m a +randomIO = liftIO $ getStdRandom random ------------------------------------------------------------------------------- -- Notes From f05c2a7d81d9b4f52a21114ff794547768a582dc Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Tue, 5 May 2020 11:23:49 +0200 Subject: [PATCH 155/170] Generate Float and Double via division (#118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Generate Float and Double via division Coverage ======== Before: 0.787% of representable Floats in the unit interval reached After: 7.874% of representable Floats in the unit interval reached (A similar enumeration for Double is impossible, but it is very likely that coverage is increased for Doubles too.) Performance =========== Before: pure/random/Float mean 331.1 μs ( +- 21.67 μs ) pure/uniformR/unbounded/Float mean 324.6 μs ( +- 2.849 μs ) pure/random/Double mean 411.3 μs ( +- 5.876 μs ) pure/uniformR/unbounded/Double mean 416.8 μs ( +- 41.93 μs ) After: pure/random/Float mean 27.32 μs ( +- 158.0 ns ) pure/uniformR/unbounded/Float mean 27.37 μs ( +- 422.0 ns ) pure/random/Double mean 27.34 μs ( +- 303.1 ns ) pure/uniformR/unbounded/Double mean 27.49 μs ( +- 983.7 ns ) * Floating point ranges inclusive in upper bound --- System/Random/Internal.hs | 55 +++++++++++---------------------------- cbits/CastFloatWord.cmm | 46 -------------------------------- random.cabal | 1 - test-legacy/RangeTest.hs | 8 +++--- test/Spec.hs | 2 +- 5 files changed, 20 insertions(+), 92 deletions(-) delete mode 100644 cbits/CastFloatWord.cmm diff --git a/System/Random/Internal.hs b/System/Random/Internal.hs index bb8044244..76e101660 100644 --- a/System/Random/Internal.hs +++ b/System/Random/Internal.hs @@ -436,25 +436,20 @@ class Uniform a where -- -- @since 1.2 class UniformRange a where - -- | Generates a value uniformly distributed over the provided range. + -- | Generates a value uniformly distributed over the provided range, which + -- is interpreted as inclusive in the lower and upper bound. -- - -- * For /integral types/, the range is interpreted as inclusive in the - -- lower and upper bound. + -- * @uniformR (1 :: Int, 4 :: Int)@ should generate values uniformly from + -- the set \(\{1,2,3,4\}\) -- - -- As an example, @uniformR (1 :: Int, 4 :: Int)@ should generate values - -- uniformly from the set \(\{1,2,3,4\}\). - -- - -- * For /non-integral types/, the range is interpreted as inclusive in the - -- lower bound and exclusive in the upper bound. - -- - -- As an example, @uniformR (1 :: Float, 4 :: Float)@ should generate - -- values uniformly from the set \(\{x\;|\;1 \le x \lt 4\}\). + -- * @uniformR (1 :: Float, 4 :: Float)@ should generate values uniformly + -- from the set \(\{x\;|\;1 \le x \le 4\}\) -- -- The following law should hold to make the function always defined: -- -- > uniformRM (a, b) = uniformRM (b, a) -- - -- @since 1.2 + -- @since 1.2 uniformRM :: MonadRandom g s m => (a, a) -> g s -> m a instance UniformRange Integer where @@ -698,35 +693,14 @@ instance UniformRange Double where return $ (h - l) * x + l -- | Turns a given uniformly distributed 'Word64' value into a uniformly --- distributed 'Double' value in the range [0, 1). +-- distributed 'Double' value in the range [0, 1]. word64ToDoubleInUnitInterval :: Word64 -> Double -word64ToDoubleInUnitInterval w64 = between1and2 - 1.0 +word64ToDoubleInUnitInterval w64 = d / m where - between1and2 = castWord64ToDouble $ (w64 `unsafeShiftR` 12) .|. 0x3ff0000000000000 + d = fromIntegral w64 :: Double + m = fromIntegral (maxBound :: Word64) :: Double {-# INLINE word64ToDoubleInUnitInterval #-} --- | These are now in 'GHC.Float' but unpatched in some versions so --- for now we roll our own. See --- https://gitlab.haskell.org/ghc/ghc/-/blob/6d172e63f3dd3590b0a57371efb8f924f1fcdf05/libraries/base/GHC/Float.hs -{-# INLINE castWord32ToFloat #-} -castWord32ToFloat :: Word32 -> Float -castWord32ToFloat (W32# w#) = F# (stgWord32ToFloat w#) - -foreign import prim "stg_word32ToFloatyg" - stgWord32ToFloat :: Word# -> Float# - -{-# INLINE castWord64ToDouble #-} -castWord64ToDouble :: Word64 -> Double -castWord64ToDouble (W64# w) = D# (stgWord64ToDouble w) - -foreign import prim "stg_word64ToDoubleyg" -#if WORD_SIZE_IN_BITS == 64 - stgWord64ToDouble :: Word# -> Double# -#else - stgWord64ToDouble :: Word64# -> Double# -#endif - - instance UniformRange Float where uniformRM (l, h) g = do w32 <- uniformWord32 g @@ -734,11 +708,12 @@ instance UniformRange Float where return $ (h - l) * x + l -- | Turns a given uniformly distributed 'Word32' value into a uniformly --- distributed 'Float' value in the range [0,1). +-- distributed 'Float' value in the range [0,1]. word32ToFloatInUnitInterval :: Word32 -> Float -word32ToFloatInUnitInterval w32 = between1and2 - 1.0 +word32ToFloatInUnitInterval w32 = f / m where - between1and2 = castWord32ToFloat $ (w32 `unsafeShiftR` 9) .|. 0x3f800000 + f = fromIntegral w32 :: Float + m = fromIntegral (maxBound :: Word32) :: Float {-# INLINE word32ToFloatInUnitInterval #-} -- The two integer functions below take an [inclusive,inclusive] range. diff --git a/cbits/CastFloatWord.cmm b/cbits/CastFloatWord.cmm deleted file mode 100644 index 2494960e1..000000000 --- a/cbits/CastFloatWord.cmm +++ /dev/null @@ -1,46 +0,0 @@ -/* From: https://gitlab.haskell.org/ghc/ghc/-/blob/6d172e63f3dd3590b0a57371efb8f924f1fcdf05/libraries/base/cbits/CastFloatWord.cmm */ -#include "Cmm.h" -#include "MachDeps.h" - -#if WORD_SIZE_IN_BITS == 64 -#define DOUBLE_SIZE_WDS 1 -#else -#define DOUBLE_SIZE_WDS 2 -#endif - -#if SIZEOF_W == 4 -#define TO_ZXW_(x) %zx32(x) -#elif SIZEOF_W == 8 -#define TO_ZXW_(x) %zx64(x) -#endif - -stg_word64ToDoubleyg(I64 w) -{ - D_ d; - P_ ptr; - - STK_CHK_GEN_N (DOUBLE_SIZE_WDS); - - reserve DOUBLE_SIZE_WDS = ptr { - I64[ptr] = w; - d = D_[ptr]; - } - - return (d); -} - -stg_word32ToFloatyg(W_ w) -{ - F_ f; - P_ ptr; - - STK_CHK_GEN_N (1); - - reserve 1 = ptr { - I32[ptr] = %lobits32(w); - f = F_[ptr]; - } - - return (f); -} - diff --git a/random.cabal b/random.cabal index 28d2f5e78..0a6c2d101 100644 --- a/random.cabal +++ b/random.cabal @@ -79,7 +79,6 @@ library System.Random.Internal System.Random.Monad - c-sources: cbits/CastFloatWord.cmm default-language: Haskell2010 ghc-options: -Wall build-depends: diff --git a/test-legacy/RangeTest.hs b/test-legacy/RangeTest.hs index ed51541c0..1c990385d 100644 --- a/test-legacy/RangeTest.hs +++ b/test-legacy/RangeTest.hs @@ -70,8 +70,8 @@ main = checkBounds "Word16" boundedRange (approxBounds random trials (undefined::Word16)) checkBounds "Word32" boundedRange (approxBounds random trials (undefined::Word32)) checkBounds "Word64" boundedRange (approxBounds random trials (undefined::Word64)) - checkBounds "Double" (True,0.0,1.0) (approxBounds random trials (undefined::Double)) - checkBounds "Float" (True,0.0,1.0) (approxBounds random trials (undefined::Float)) + checkBounds "Double" (False,0.0,1.0) (approxBounds random trials (undefined::Double)) + checkBounds "Float" (False,0.0,1.0) (approxBounds random trials (undefined::Float)) checkBounds "CChar" boundedRange (approxBounds random trials (undefined:: CChar)) checkBounds "CSChar" boundedRange (approxBounds random trials (undefined:: CSChar)) @@ -111,8 +111,8 @@ main = checkBounds "Word16 R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined::Word16)) checkBounds "Word32 R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined::Word32)) checkBounds "Word64 R" (False,0,200) (approxBounds (randomR (0,200)) trials (undefined::Word64)) - checkBounds "Double R" (True,10.0,77.0) (approxBounds (randomR (10,77)) trials (undefined::Double)) - checkBounds "Float R" (True,10.0,77.0) (approxBounds (randomR (10,77)) trials (undefined::Float)) + checkBounds "Double R" (False,10.0,77.0) (approxBounds (randomR (10,77)) trials (undefined::Double)) + checkBounds "Float R" (False,10.0,77.0) (approxBounds (randomR (10,77)) trials (undefined::Float)) checkBounds "CChar R" (False,0,100) (approxBounds (randomR (0,100)) trials (undefined:: CChar)) checkBounds "CSChar R" (False,-100,100) (approxBounds (randomR (-100,100)) trials (undefined:: CSChar)) diff --git a/test/Spec.hs b/test/Spec.hs index 1bd0f8a20..97aec11f6 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -129,7 +129,7 @@ floatingSpec :: => TestTree floatingSpec = testGroup ("(" ++ showsType @a ")") - [ SC.testProperty "uniformR" $ seeded $ Range.uniformRangeWithinExcluded @_ @a + [ SC.testProperty "uniformR" $ seeded $ Range.uniformRangeWithin @_ @a -- TODO: Add more tests ] From 0058d77cdf615792e41267cabd27c4a81ed9e59a Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Tue, 5 May 2020 20:08:53 +0300 Subject: [PATCH 156/170] Minor cleanups: * Remove -fobject-code compilation, since Cmm was removed * Fix example in cabal file * Take care of some compile warnings in legacy benchmarks --- .ghci | 1 - System/Random.hs | 1 + random.cabal | 5 +++-- test/doctests.hs | 4 +--- 4 files changed, 5 insertions(+), 6 deletions(-) delete mode 100755 .ghci diff --git a/.ghci b/.ghci deleted file mode 100755 index d42a8637a..000000000 --- a/.ghci +++ /dev/null @@ -1 +0,0 @@ -:set -fobject-code diff --git a/System/Random.hs b/System/Random.hs index be16853bf..3741ce0d3 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -312,6 +312,7 @@ randomIO = liftIO $ getStdRandom random -- -- It produces a full 'Word32' of randomness per iteration. -- +-- >>> import Data.Bits -- >>> :{ -- let stepGen :: PCGen -> (Word32, PCGen) -- stepGen (PCGen state inc) = let diff --git a/random.cabal b/random.cabal index 0a6c2d101..c5f3e7670 100644 --- a/random.cabal +++ b/random.cabal @@ -19,7 +19,7 @@ description: As an example, here is how you can simulate rolls of a six-sided die using 'System.Random.uniformR': . - >>> let roll = flip uniformR (1, 6) :: RandomGen g => g -> (Word8, g) + >>> let roll = uniformR (1, 6) :: RandomGen g => g -> (Word8, g) >>> let rolls = unfoldr (Just . roll) :: RandomGen g => g -> [Word8] >>> let pureGen = mkStdGen 42 >>> take 10 (rolls pureGen) :: [Word8] @@ -50,6 +50,7 @@ description: number generators. In this example, we use the one provided in the package: . + >>> import System.Random.MWC as MWC >>> let rollM = uniformRM (1, 6) :: MonadRandom g s m => g s -> m Word8 >>> monadicGen <- MWC.create >>> (replicateM 10 . rollM) monadicGen :: m [Word8] @@ -143,7 +144,7 @@ benchmark legacy-bench hs-source-dirs: bench-legacy other-modules: BinSearch default-language: Haskell2010 - ghc-options: -Wall -O2 -threaded -rtsopts -with-rtsopts=-N + ghc-options: -Wall -O2 -threaded -rtsopts -with-rtsopts=-N -Wno-deprecations build-depends: base >=4.10 && <5, random -any, diff --git a/test/doctests.hs b/test/doctests.hs index 5fcfbe447..bbf7787f8 100644 --- a/test/doctests.hs +++ b/test/doctests.hs @@ -9,6 +9,4 @@ main = do traverse_ putStrLn args doctest args where - -- '-fobject-code' is required to get the doctests to build without - -- tripping over the Cmm bits. - args = ["-fobject-code"] ++ flags ++ pkgs ++ module_sources + args = flags ++ pkgs ++ module_sources From f1cbbb7b2a407dd95f60bd19f6aa75a9eb1d228e Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Tue, 5 May 2020 21:53:40 +0300 Subject: [PATCH 157/170] Lagacy benchmarks code formatting and cleanup. Get rid of compilation warnigns --- bench-legacy/BinSearch.hs | 173 ++++++++------- bench-legacy/SimpleRNGBench.hs | 385 ++++++++++++++------------------- 2 files changed, 256 insertions(+), 302 deletions(-) diff --git a/bench-legacy/BinSearch.hs b/bench-legacy/BinSearch.hs index f61164855..81a57930e 100644 --- a/bench-legacy/BinSearch.hs +++ b/bench-legacy/BinSearch.hs @@ -1,5 +1,5 @@ -{- +{- Binary search over benchmark input sizes. There are many good ways to measure the time it takes to perform a @@ -16,7 +16,7 @@ An alternative approach is to kill the computation after a certain amount of time and observe how much work it has completed. -} -module BinSearch +module BinSearch ( binSearch ) @@ -39,81 +39,88 @@ import Prelude hiding (min,max,log) -- between min and max, then it will then run for N trials and -- return the median (input,time-in-seconds) pair. binSearch :: Bool -> Integer -> (Double,Double) -> (Integer -> IO ()) -> IO (Integer, Double) -binSearch verbose trials (min,max) kernel = - do - when(verbose)$ putStrLn$ "[binsearch] Binary search for input size resulting in time in range "++ show (min,max) - - let desired_exec_length = 1.0 - good_trial t = (toRational t <= toRational max) && (toRational t >= toRational min) - - -- At some point we must give up... - loop n | n > ((2::Integer) ^ (100::Integer)) = error "ERROR binSearch: This function doesn't seem to scale in proportion to its last argument." - - -- Not allowed to have "0" size input, bump it back to one: - loop 0 = loop 1 - - loop n = - do - when(verbose)$ putStr$ "[binsearch:"++ show n ++ "] " - time <- timeit$ kernel n - when(verbose)$ putStrLn$ "Time consumed: "++ show time - let rate = fromIntegral n / time - - -- [2010.06.09] Introducing a small fudge factor to help our guess get over the line: - let initial_fudge_factor = 1.10 - fudge_factor = 1.01 -- Even in the steady state we fudge a little - guess = desired_exec_length * rate - - -- TODO: We should keep more history here so that we don't re-explore input space we have already explored. - -- This is a balancing act because of randomness in execution time. - - if good_trial time - then do - when(verbose)$ putStrLn$ "[binsearch] Time in range. LOCKING input size and performing remaining trials." - print_trial 1 n time - lockin (trials-1) n [time] - - -- Here we're still in the doubling phase: - else if time < 0.100 - then loop (2*n) - - else do when(verbose)$ - putStrLn$ "[binsearch] Estimated rate to be " - ++show (round rate::Integer)++" per second. Trying to scale up..." - - -- Here we've exited the doubling phase, but we're making our first guess as to how big a real execution should be: - if time > 0.100 && time < 0.33 * desired_exec_length - then do when(verbose)$ putStrLn$ "[binsearch] (Fudging first guess a little bit extra)" - loop (round$ guess * initial_fudge_factor) - else loop (round$ guess * fudge_factor) - - -- Termination condition: Done with all trials. - lockin 0 n log = do when(verbose)$ putStrLn$ "[binsearch] Time-per-unit for all trials: "++ - (concat $ intersperse " " (map (show . (/ toDouble n) . toDouble) $ sort log)) - return (n, log !! ((length log) `quot` 2)) -- Take the median - - lockin trials_left n log = - do when(verbose)$ putStrLn$ "[binsearch]------------------------------------------------------------" - time <- timeit$ kernel n - -- hFlush stdout - print_trial (trials - trials_left +1 ) n time - -- when(verbose)$ hFlush stdout - lockin (trials_left - 1) n (time : log) - - print_trial :: Integer -> Integer -> NominalDiffTime -> IO () - print_trial trialnum n time = - let rate = fromIntegral n / time - timeperunit = time / fromIntegral n - in - when(verbose)$ putStrLn$ "[binsearch] TRIAL: "++show trialnum ++ - " secPerUnit: "++ showTime timeperunit ++ - " ratePerSec: "++ show (rate) ++ - " seconds: "++showTime time - - +binSearch verbose trials (min, max) kernel = do + when verbose $ + putStrLn $ + "[binsearch] Binary search for input size resulting in time in range " ++ + show (min, max) + let desired_exec_length = 1.0 + good_trial t = + (toRational t <= toRational max) && (toRational t >= toRational min) + -- At some point we must give up... + loop n + | n > ((2 :: Integer) ^ (100 :: Integer)) = + error + "ERROR binSearch: This function doesn't seem to scale in proportion to its last argument." + -- Not allowed to have "0" size input, bump it back to one: + loop 0 = loop 1 + loop n = do + when verbose $ putStr $ "[binsearch:" ++ show n ++ "] " + time <- timeit $ kernel n + when verbose $ putStrLn $ "Time consumed: " ++ show time + let rate = fromIntegral n / time + -- [2010.06.09] Introducing a small fudge factor to help our guess get over the line: + let initial_fudge_factor = 1.10 + fudge_factor = 1.01 -- Even in the steady state we fudge a little + guess = desired_exec_length * rate + -- TODO: We should keep more history here so that we don't re-explore input space we + -- have already explored. This is a balancing act because of randomness in + -- execution time. + if good_trial time + then do + when verbose $ + putStrLn + "[binsearch] Time in range. LOCKING input size and performing remaining trials." + print_trial 1 n time + lockin (trials - 1) n [time] + else if time < 0.100 + then loop (2 * n) + else do + when verbose $ + putStrLn $ + "[binsearch] Estimated rate to be " ++ + show (round rate :: Integer) ++ + " per second. Trying to scale up..." + -- Here we've exited the doubling phase, but we're making our + -- first guess as to how big a real execution should be: + if time > 0.100 && time < 0.33 * desired_exec_length + then do + when verbose $ + putStrLn + "[binsearch] (Fudging first guess a little bit extra)" + loop (round $ guess * initial_fudge_factor) + else loop (round $ guess * fudge_factor) + -- Termination condition: Done with all trials. + lockin 0 n log = do + when verbose $ + putStrLn $ + "[binsearch] Time-per-unit for all trials: " ++ + concat + (intersperse " " (map (show . (/ toDouble n) . toDouble) $ sort log)) + return (n, log !! (length log `quot` 2)) -- Take the median + lockin trials_left n log = do + when verbose $ + putStrLn + "[binsearch]------------------------------------------------------------" + time <- timeit $ kernel n + -- hFlush stdout + print_trial (trials - trials_left + 1) n time + -- whenverbose$ hFlush stdout + lockin (trials_left - 1) n (time : log) + print_trial :: Integer -> Integer -> NominalDiffTime -> IO () + print_trial trialnum n time = + let rate = fromIntegral n / time + timeperunit = time / fromIntegral n + in when verbose $ + putStrLn $ + "[binsearch] TRIAL: " ++ + show trialnum ++ + " secPerUnit: " ++ + showTime timeperunit ++ + " ratePerSec: " ++ show rate ++ " seconds: " ++ showTime time + (n, t) <- loop 1 + return (n, fromRational $ toRational t) - (n,t) <- loop 1 - return (n, fromRational$ toRational t) showTime :: NominalDiffTime -> String showTime t = show ((fromRational $ toRational t) :: Double) @@ -125,16 +132,16 @@ toDouble = fromRational . toRational -- Could use cycle counters here.... but the point of this is to time -- things on the order of a second. timeit :: IO () -> IO NominalDiffTime -timeit io = - do strt <- getCurrentTime - io - end <- getCurrentTime - return (diffUTCTime end strt) +timeit io = do + strt <- getCurrentTime + io + end <- getCurrentTime + return (diffUTCTime end strt) {- test :: IO (Integer,Double) -test = +test = binSearch True 3 (1.0, 1.05) - (\n -> + (\n -> do v <- newIORef 0 forM_ [1..n] $ \i -> do old <- readIORef v diff --git a/bench-legacy/SimpleRNGBench.hs b/bench-legacy/SimpleRNGBench.hs index a2f919d7d..297935202 100644 --- a/bench-legacy/SimpleRNGBench.hs +++ b/bench-legacy/SimpleRNGBench.hs @@ -14,7 +14,7 @@ import System.Console.GetOpt import GHC.Conc import Control.Concurrent -import Control.Monad +import Control.Monad import Control.Exception import Data.IORef @@ -32,24 +32,12 @@ import Foreign.Storable (peek,poke) import Prelude hiding (last,sum) import BinSearch -#ifdef TEST_COMPETITORS -import System.Random.Mersenne.Pure64 -import System.Random.MWC -import Control.Monad.Primitive --- import System.IO.Unsafe -import GHC.IO -#endif - ---------------------------------------------------------------------------------------------------- -- Miscellaneous helpers: -- Readable large integer printing: commaint :: Show a => a -> String -commaint n = - reverse $ concat $ - intersperse "," $ - chunk 3 $ - reverse (show n) +commaint n = reverse $ concat $ intersperse "," $ chunk 3 $ reverse (show n) padleft :: Int -> String -> String padleft n str | length str >= n = str @@ -60,70 +48,55 @@ padright n str | length str >= n = str padright n str | otherwise = str ++ take (n - length str) (repeat ' ') fmt_num :: (RealFrac a, PrintfArg a) => a -> String -fmt_num n = if n < 100 - then printf "%.2f" n - else commaint (round n :: Integer) +fmt_num n = + if n < 100 + then printf "%.2f" n + else commaint (round n :: Integer) -- Measure clock frequency, spinning rather than sleeping to try to -- stay on the same core. measureFreq :: IO Int64 -measureFreq = do +measureFreq = do let second = 1000 * 1000 * 1000 * 1000 -- picoseconds are annoying - t1 <- rdtsc + t1 <- rdtsc start <- getCPUTime - let loop !n !last = - do t2 <- rdtsc - when (t2 < last) $ - putStrLn$ "COUNTERS WRAPPED "++ show (last,t2) - cput <- getCPUTime - if (cput - start < second) - then loop (n+1) t2 - else return (n,t2) - (n,t2) <- loop 0 t1 - putStrLn$ " Approx getCPUTime calls per second: "++ commaint (n::Int64) - when (t2 < t1) $ - putStrLn$ "WARNING: rdtsc not monotonically increasing, first "++show t1++" then "++show t2++" on the same OS thread" - - return$ fromIntegral (t2 - t1) + let loop !n !last = do + t2 <- rdtsc + when (t2 < last) $ putStrLn $ "COUNTERS WRAPPED " ++ show (last, t2) + cput <- getCPUTime + if cput - start < second + then loop (n + 1) t2 + else return (n, t2) + (n, t2) <- loop 0 t1 + putStrLn $ " Approx getCPUTime calls per second: " ++ commaint (n :: Int64) + when (t2 < t1) $ + putStrLn $ + "WARNING: rdtsc not monotonically increasing, first " ++ + show t1 ++ " then " ++ show t2 ++ " on the same OS thread" + return $ fromIntegral (t2 - t1) ---------------------------------------------------------------------------------------------------- -- Test overheads without actually generating any random numbers: data NoopRNG = NoopRNG -instance RandomGen NoopRNG where - next g = (0,g) -#ifdef ENABLE_SPLITTABLEGEN - genRange _ = (0,0) -instance SplittableGen NoopRNG where -#endif - split g = (g,g) +instance RandomGen NoopRNG where + next g = (0, g) + genRange _ = (0, 0) + split g = (g, g) -- An RNG generating only 0 or 1: data BinRNG = BinRNG StdGen -instance RandomGen BinRNG where +instance RandomGen BinRNG where next (BinRNG g) = (x `mod` 2, BinRNG g') - where (x,g') = next g -#ifdef ENABLE_SPLITTABLEGEN - genRange _ = (0,1) -instance SplittableGen BinRNG where -#endif + where + (x, g') = next g + genRange _ = (0, 1) split (BinRNG g) = (BinRNG g1, BinRNG g2) - where (g1,g2) = split g - + where + (g1, g2) = split g -#ifdef TEST_COMPETITORS -data MWCRNG = MWCRNG (Gen (PrimState IO)) --- data MWCRNG = MWCRNG GenIO -instance RandomGen MWCRNG where - -- For testing purposes we hack this to be non-monadic: --- next g@(MWCRNG gen) = unsafePerformIO $ - next g@(MWCRNG gen) = unsafeDupablePerformIO $ - do v <- uniform gen - return (v, g) -#endif - ---------------------------------------------------------------------------------------------------- -- Drivers to get random numbers repeatedly. @@ -135,188 +108,162 @@ type Kern = Int -> Ptr Int -> IO () -- foreign import ccall unsafe "stdlib.hs" rand :: IO Int {-# INLINE timeit #-} -timeit :: (Random a, RandomGen g) => - Int -> Int64 -> String -> g -> (g -> (a,g)) -> IO () -timeit numthreads freq msg gen nxt = - do - counters <- forM [1..numthreads] (const$ newIORef (1::Int64)) - tids <- forM counters $ \counter -> - forkIO $ infloop counter (nxt gen) - threadDelay (1000*1000) -- One second - mapM_ killThread tids - - finals <- mapM readIORef counters - let mean :: Double = fromIntegral (foldl1 (+) finals) / fromIntegral numthreads - cycles_per :: Double = fromIntegral freq / mean - printResult (round mean :: Int64) msg cycles_per - - where - infloop !counter !(!_,!g) = - do incr counter - infloop counter (nxt g) - - incr !counter = - do -- modifyIORef counter (+1) -- Not strict enough! - c <- readIORef counter - let c' = c+1 - _ <- evaluate c' - writeIORef counter c' +timeit :: (Random a, RandomGen g) => Int -> Int64 -> String -> g -> (g -> (a,g)) -> IO () +timeit numthreads freq msg gen nxt = do + counters <- forM [1 .. numthreads] (const $ newIORef (1 :: Int64)) + tids <- forM counters $ \counter -> forkIO $ infloop counter (nxt gen) + threadDelay (1000 * 1000) -- One second + mapM_ killThread tids + finals <- mapM readIORef counters + let mean :: Double = + fromIntegral (foldl1 (+) finals) / fromIntegral numthreads + cycles_per :: Double = fromIntegral freq / mean + printResult (round mean :: Int64) msg cycles_per + where + infloop !counter (!_, !g) = do + incr counter + infloop counter (nxt g) + incr !counter + -- modifyIORef counter (+1) -- Not strict enough! + = do + c <- readIORef counter + let c' = c + 1 + _ <- evaluate c' + writeIORef counter c' -- This function times an IO function on one or more threads. Rather -- than running a fixed number of iterations, it uses a binary search -- to find out how many iterations can be completed in a second. timeit_foreign :: Int -> Int64 -> String -> (Int -> Ptr Int -> IO ()) -> IO Int64 -timeit_foreign numthreads freq msg ffn = do - ptr :: ForeignPtr Int <- mallocForeignPtr - - let kern = if numthreads == 1 - then ffn - else replicate_kernel numthreads ffn - wrapped n = withForeignPtr ptr (kern$ fromIntegral n) - (n,t) <- binSearch False 1 (1.0, 1.05) wrapped - +timeit_foreign numthreads freq msg ffn = do + ptr :: ForeignPtr Int <- mallocForeignPtr + let kern = + if numthreads == 1 + then ffn + else replicate_kernel numthreads ffn + wrapped n = withForeignPtr ptr (kern $ fromIntegral n) + (n, t) <- binSearch False 1 (1.0, 1.05) wrapped let total_per_second = round $ fromIntegral n * (1 / t) cycles_per = fromIntegral freq * t / fromIntegral n printResult total_per_second msg cycles_per return total_per_second - - where - -- This lifts a C kernel to operate simultaneously on N threads. - replicate_kernel :: Int -> Kern -> Kern - replicate_kernel nthreads kern n ptr = do - ptrs <- forM [1..nthreads] - (const mallocForeignPtr) - tmpchan <- newChan - -- let childwork = ceiling$ fromIntegral n / fromIntegral nthreads - let childwork = n -- Keep it the same.. interested in per-thread throughput. - -- Fork/join pattern: - _ <- forM ptrs $ \pt -> forkIO $ - withForeignPtr pt $ \p -> do - kern (fromIntegral childwork) p - result <- peek p - writeChan tmpchan result - - results <- forM [1..nthreads] $ \_ -> - readChan tmpchan - -- Meaningless semantics here... sum the child ptrs and write to the input one: - poke ptr (foldl1 (+) results) - return () + -- This lifts a C kernel to operate simultaneously on N threads. + where + replicate_kernel :: Int -> Kern -> Kern + replicate_kernel nthreads kern n ptr = do + ptrs <- forM [1 .. nthreads] (const mallocForeignPtr) + tmpchan <- newChan + -- let childwork = ceiling$ fromIntegral n / fromIntegral nthreads + let childwork = n -- Keep it the same.. interested in per-thread throughput. + -- Fork/join pattern: + forM_ ptrs $ \pt -> + forkIO $ + withForeignPtr pt $ \p -> do + kern (fromIntegral childwork) p + result <- peek p + writeChan tmpchan result + results <- forM [1 .. nthreads] $ \_ -> readChan tmpchan + -- Meaningless semantics here... sum the child ptrs and write to the input one: + poke ptr (foldl1 (+) results) printResult :: Int64 -> String -> Double -> IO () -printResult total msg cycles_per = - putStrLn$ " "++ padleft 11 (commaint total) ++" randoms generated "++ padright 27 ("["++msg++"]") ++" ~ " - ++ fmt_num cycles_per ++" cycles/int" +printResult total msg cycles_per = + putStrLn $ + " " ++ + padleft 11 (commaint total) ++ + " randoms generated " ++ + padright 27 ("[" ++ msg ++ "]") ++ + " ~ " ++ fmt_num cycles_per ++ " cycles/int" ---------------------------------------------------------------------------------------------------- -- Main Script -data Flag = NoC | Help +data Flag = NoC | Help deriving (Show, Eq) options :: [OptDescr Flag] -options = +options = [ Option ['h'] ["help"] (NoArg Help) "print program help" , Option [] ["noC"] (NoArg NoC) "omit C benchmarks, haskell only" ] main :: IO () -main = do - argv <- getArgs - let (opts,_,other) = getOpt Permute options argv - - when (not$ null other) $ do - putStrLn$ "ERROR: Unrecognized options: " - mapM_ putStr other - exitFailure - - when (Help `elem` opts) $ do - putStr$ usageInfo "Benchmark random number generation" options - exitSuccess - - putStrLn$ "\nHow many random numbers can we generate in a second on one thread?" - - t1 <- rdtsc - t2 <- rdtsc - putStrLn (" Cost of rdtsc (ffi call): " ++ show (t2 - t1)) - - freq <- measureFreq - putStrLn$ " Approx clock frequency: " ++ commaint freq - - let - randInt = random :: RandomGen g => g -> (Int,g) - randWord16 = random :: RandomGen g => g -> (Word16,g) - randFloat = random :: RandomGen g => g -> (Float,g) - randCFloat = random :: RandomGen g => g -> (CFloat,g) - randDouble = random :: RandomGen g => g -> (Double,g) - randCDouble = random :: RandomGen g => g -> (CDouble,g) - randInteger = random :: RandomGen g => g -> (Integer,g) - randBool = random :: RandomGen g => g -> (Bool,g) - randChar = random :: RandomGen g => g -> (Char,g) - - gen = mkStdGen 23852358661234 - gamut th = do - putStrLn$ " First, timing System.Random.next:" - timeit th freq "constant zero gen" NoopRNG next - timeit th freq "System.Random stdGen/next" gen next - - putStrLn$ "\n Second, timing System.Random.random at different types:" - timeit th freq "System.Random Ints" gen randInt - timeit th freq "System.Random Word16" gen randWord16 - timeit th freq "System.Random Floats" gen randFloat - timeit th freq "System.Random CFloats" gen randCFloat - timeit th freq "System.Random Doubles" gen randDouble - timeit th freq "System.Random CDoubles" gen randCDouble - timeit th freq "System.Random Integers" gen randInteger - timeit th freq "System.Random Bools" gen randBool - timeit th freq "System.Random Chars" gen randChar - -#ifdef TEST_COMPETITORS - putStrLn$ "\n Next test other RNG packages on Hackage:" - let gen_mt = pureMT 39852 - randInt2 = random :: RandomGen g => g -> (Int,g) - randFloat2 = random :: RandomGen g => g -> (Float,g) - timeit th freq "System.Random.Mersenne.Pure64 next" gen_mt next - timeit th freq "System.Random.Mersenne.Pure64 Ints" gen_mt randInt2 - timeit th freq "System.Random.Mersenne.Pure64 Floats" gen_mt randFloat2 - --- gen_mwc <- create - withSystemRandom $ \ gen_mwc -> do - let randInt3 = random :: RandomGen g => g -> (Int,g) - randFloat3 = random :: RandomGen g => g -> (Float,g) - - timeit th freq "System.Random.MWC next" (MWCRNG gen_mwc) next - timeit th freq "System.Random.MWC Ints" (MWCRNG gen_mwc) randInt3 - timeit th freq "System.Random.MWC Floats" (MWCRNG gen_mwc) randFloat3 - -#endif - - putStrLn$ "\n Next timing range-restricted System.Random.randomR:" - timeit th freq "System.Random Ints" gen (randomR (-100, 100::Int)) - timeit th freq "System.Random Word16s" gen (randomR (-100, 100::Word16)) - timeit th freq "System.Random Floats" gen (randomR (-100, 100::Float)) - timeit th freq "System.Random CFloats" gen (randomR (-100, 100::CFloat)) - timeit th freq "System.Random Doubles" gen (randomR (-100, 100::Double)) - timeit th freq "System.Random CDoubles" gen (randomR (-100, 100::CDouble)) - timeit th freq "System.Random Integers" gen (randomR (-100, 100::Integer)) - timeit th freq "System.Random Bools" gen (randomR (False, True::Bool)) - timeit th freq "System.Random Chars" gen (randomR ('a', 'z')) - timeit th freq "System.Random BIG Integers" gen (randomR (0, (2::Integer) ^ (5000::Int))) - - -- when (not$ NoC `elem` opts) $ do - -- putStrLn$ " Comparison to C's rand():" - -- timeit_foreign th freq "ptr store in C loop" store_loop - -- timeit_foreign th freq "rand/store in C loop" blast_rands - -- timeit_foreign th freq "rand in Haskell loop" (\n ptr -> forM_ [1..n]$ \_ -> rand ) - -- timeit_foreign th freq "rand/store in Haskell loop" (\n ptr -> forM_ [1..n]$ \_ -> do n <- rand; poke ptr n ) - -- return () - - -- Test with 1 thread and numCapabilities threads: - gamut 1 - when (numCapabilities > 1) $ do - putStrLn$ "\nNow "++ show numCapabilities ++" threads, reporting mean randoms-per-second-per-thread:" - gamut numCapabilities - return () - - putStrLn$ "Finished." +main = do + argv <- getArgs + let (opts,_,other) = getOpt Permute options argv + + unless (null other) $ do + putStrLn "ERROR: Unrecognized options: " + mapM_ putStr other + exitFailure + + when (Help `elem` opts) $ do + putStr $ usageInfo "Benchmark random number generation" options + exitSuccess + + putStrLn "\nHow many random numbers can we generate in a second on one thread?" + + t1 <- rdtsc + t2 <- rdtsc + putStrLn (" Cost of rdtsc (ffi call): " ++ show (t2 - t1)) + + freq <- measureFreq + putStrLn $ " Approx clock frequency: " ++ commaint freq + + let randInt = random :: RandomGen g => g -> (Int,g) + randWord16 = random :: RandomGen g => g -> (Word16,g) + randFloat = random :: RandomGen g => g -> (Float,g) + randCFloat = random :: RandomGen g => g -> (CFloat,g) + randDouble = random :: RandomGen g => g -> (Double,g) + randCDouble = random :: RandomGen g => g -> (CDouble,g) + randInteger = random :: RandomGen g => g -> (Integer,g) + randBool = random :: RandomGen g => g -> (Bool,g) + randChar = random :: RandomGen g => g -> (Char,g) + + gen = mkStdGen 23852358661234 + gamut th = do + putStrLn " First, timing System.Random.next:" + timeit th freq "constant zero gen" NoopRNG next + timeit th freq "System.Random stdGen/next" gen next + + putStrLn "\n Second, timing System.Random.random at different types:" + timeit th freq "System.Random Ints" gen randInt + timeit th freq "System.Random Word16" gen randWord16 + timeit th freq "System.Random Floats" gen randFloat + timeit th freq "System.Random CFloats" gen randCFloat + timeit th freq "System.Random Doubles" gen randDouble + timeit th freq "System.Random CDoubles" gen randCDouble + timeit th freq "System.Random Integers" gen randInteger + timeit th freq "System.Random Bools" gen randBool + timeit th freq "System.Random Chars" gen randChar + + putStrLn "\n Next timing range-restricted System.Random.randomR:" + timeit th freq "System.Random Ints" gen (randomR (-100, 100::Int)) + timeit th freq "System.Random Word16s" gen (randomR ( 100, 300::Word16)) + timeit th freq "System.Random Floats" gen (randomR (-100, 100::Float)) + timeit th freq "System.Random CFloats" gen (randomR (-100, 100::CFloat)) + timeit th freq "System.Random Doubles" gen (randomR (-100, 100::Double)) + timeit th freq "System.Random CDoubles" gen (randomR (-100, 100::CDouble)) + timeit th freq "System.Random Integers" gen (randomR (-100, 100::Integer)) + timeit th freq "System.Random Bools" gen (randomR (False, True::Bool)) + timeit th freq "System.Random Chars" gen (randomR ('a', 'z')) + timeit th freq "System.Random BIG Integers" gen (randomR (0, (2::Integer) ^ (5000::Int))) + + -- when (not$ NoC `elem` opts) $ do + -- putStrLn$ " Comparison to C's rand():" + -- timeit_foreign th freq "ptr store in C loop" store_loop + -- timeit_foreign th freq "rand/store in C loop" blast_rands + -- timeit_foreign th freq "rand in Haskell loop" (\n ptr -> forM_ [1..n]$ \_ -> rand ) + -- timeit_foreign th freq "rand/store in Haskell loop" (\n ptr -> forM_ [1..n]$ \_ -> do n <- rand; poke ptr n ) + -- return () + + -- Test with 1 thread and numCapabilities threads: + gamut 1 + when (numCapabilities > 1) $ do + putStrLn $ "\nNow "++ show numCapabilities ++" threads, reporting mean randoms-per-second-per-thread:" + void $ gamut numCapabilities + + putStrLn "Finished." + From 424ccdf0baef40482bd62b8ddf86d24a9bd28e18 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Wed, 6 May 2020 14:42:53 +0200 Subject: [PATCH 158/170] Remove unnecessary CPP uses --- bench-legacy/SimpleRNGBench.hs | 2 +- test-legacy/RangeTest.hs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/bench-legacy/SimpleRNGBench.hs b/bench-legacy/SimpleRNGBench.hs index 297935202..61df451f5 100644 --- a/bench-legacy/SimpleRNGBench.hs +++ b/bench-legacy/SimpleRNGBench.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE BangPatterns, CPP, ScopedTypeVariables, ForeignFunctionInterface #-} +{-# LANGUAGE BangPatterns, ScopedTypeVariables, ForeignFunctionInterface #-} {-# OPTIONS_GHC -fwarn-unused-imports #-} -- | A simple script to do some very basic timing of the RNGs. diff --git a/test-legacy/RangeTest.hs b/test-legacy/RangeTest.hs index 1c990385d..78cc17547 100644 --- a/test-legacy/RangeTest.hs +++ b/test-legacy/RangeTest.hs @@ -1,5 +1,3 @@ -{-# LANGUAGE CPP #-} - module RangeTest (main) where import Control.Monad From 85e6b5c6863ef26559af923ec08c697c0d579e8f Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Wed, 6 May 2020 14:43:32 +0200 Subject: [PATCH 159/170] Fix "default-language" warning, run cabal format --- random.cabal | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/random.cabal b/random.cabal index c5f3e7670..c05dff267 100644 --- a/random.cabal +++ b/random.cabal @@ -144,7 +144,9 @@ benchmark legacy-bench hs-source-dirs: bench-legacy other-modules: BinSearch default-language: Haskell2010 - ghc-options: -Wall -O2 -threaded -rtsopts -with-rtsopts=-N -Wno-deprecations + ghc-options: + -Wall -O2 -threaded -rtsopts -with-rtsopts=-N -Wno-deprecations + build-depends: base >=4.10 && <5, random -any, @@ -153,10 +155,11 @@ benchmark legacy-bench time >=1.8 && <1.11 benchmark bench - type: exitcode-stdio-1.0 - main-is: Main.hs - hs-source-dirs: bench - ghc-options: -Wall -O2 + type: exitcode-stdio-1.0 + main-is: Main.hs + hs-source-dirs: bench + default-language: Haskell2010 + ghc-options: -Wall -O2 build-depends: base >=4.10 && <5, gauge >=0.2.3 && <0.3, From 609ebc311f82b0af6c538d0468f0153a5899e388 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 7 May 2020 13:49:56 +0200 Subject: [PATCH 160/170] Add stack artifacts to .gitignore --- .gitignore | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 41c6d8c61..488b83d15 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,10 @@ GNUmakefile dist-install/ ghc.mk -dist -.cabal-sandbox +.cabal-sandbox/ +.stack-work/ +cabal.project.local cabal.sandbox.config +dist-newstyle/ +dist/ +stack.yaml.lock From 2013e25b09410d0da4594dca31180e9632991993 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 7 May 2020 14:15:15 +0200 Subject: [PATCH 161/170] Document uniform and uniformR Docs ported from #98 --- System/Random.hs | 33 ++++++++++++++++++++++++++++ System/Random/Monad.hs | 50 ++++++++++++++++++------------------------ 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 3741ce0d3..721ea122a 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -14,6 +14,9 @@ module System.Random -- * Introduction -- $introduction + -- * Usage + -- $usagepure + -- * Pure number generator interface -- $interfaces RandomGen(..) @@ -76,6 +79,31 @@ import qualified System.Random.SplitMix as SM -- package. -- Programmers may, of course, supply their own instances of 'RandomGen'. -- +-- $ usagepure +-- +-- In pure code, use 'uniform' and 'uniformR' to generate pseudo-random values +-- with a pure pseudo-random number generator like 'StdGen'. +-- +-- >>> :{ +-- let rolls :: RandomGen g => Int -> g -> [Word8] +-- rolls n = take n . unfoldr (Just . uniformR (1, 6)) +-- pureGen = mkStdGen 137 +-- in +-- rolls 10 pureGen :: [Word8] +-- :} +-- [1,2,6,6,5,1,4,6,5,4] +-- +-- To run use a /monadic/ pseudo-random computation in pure code with a pure +-- pseudo-random number generator, use 'runGenState' and its variants. +-- +-- >>> :{ +-- let rollsM :: MonadRandom g s m => Int -> g s -> m [Word8] +-- rollsM n = replicateM n . uniformRM (1, 6) +-- pureGen = mkStdGen 137 +-- in +-- runGenState_ pureGen (rollsM 10) :: [Word8] +-- :} +-- [1,2,6,6,5,1,4,6,5,4] ------------------------------------------------------------------------------- -- Pseudo-random number generator interfaces @@ -417,3 +445,8 @@ randomIO = liftIO $ getStdRandom random -- International Conference on Object Oriented Programming Systems Languages & -- Applications (OOPSLA '14). ACM, New York, NY, USA, 453-472. DOI: -- + +-- $setup +-- +-- >>> import Control.Monad (replicateM) +-- >>> import Data.List (unfoldr) diff --git a/System/Random/Monad.hs b/System/Random/Monad.hs index 309e8bfad..3c98b7db0 100644 --- a/System/Random/Monad.hs +++ b/System/Random/Monad.hs @@ -109,43 +109,35 @@ import System.Random.Internal -- -- $usagemonadic -- --- == How to generate pseudo-random values in monadic code --- -- In monadic code, use the relevant 'Uniform' and 'UniformRange' instances to -- generate pseudo-random values via 'uniformM' and 'uniformRM', respectively. -- --- As an example, @rolls@ generates @n@ pseudo-random values of @Word8@ in the --- range @[1, 6]@. +-- As an example, @rollsM@ generates @n@ pseudo-random values of @Word8@ in the +-- range @[1, 6]@ in a 'MonadRandom' context; given a /monadic/ pseudo-random +-- number generator, you can run this probabilistic computation as follows: -- -- >>> :{ --- let rolls :: MonadRandom g s m => Int -> g s -> m [Word8] --- rolls n = replicateM n . uniformRM (1, 6) +-- let rollsM :: MonadRandom g s m => Int -> g s -> m [Word8] +-- rollsM n = replicateM n . uniformRM (1, 6) +-- in do +-- monadicGen <- MWC.create +-- rollsM 10 monadicGen :: IO [Word8] -- :} +-- [4,1,2,4,4,5,2,1,5,4] -- --- Given a /monadic/ pseudo-random number generator, you can run this --- probabilistic computation as follows: --- --- >>> monadicGen <- MWC.create --- >>> rolls 12 monadicGen :: IO [Word8] --- [4,1,2,4,4,5,2,1,5,4,6,6] --- --- Given a /pure/ pseudo-random number generator, you can run it in an 'IO' or --- 'ST' context by first applying a monadic adapter like 'AtomicGen', 'IOGen' --- or 'STGen' and then running it with 'runGenM'. --- --- >>> let pureGen = mkStdGen 41 --- >>> runGenM_ (IOGen pureGen) (rolls 10) :: IO [Word8] --- [6,4,5,1,1,3,2,4,5,5] +-- Given a /pure/ pseudo-random number generator, you can run the monadic +-- pseudo-random number computation @rollsM@ in an 'IO' or 'ST' context by +-- first applying a monadic adapter like 'AtomicGen', 'IOGen' or 'STGen' to the +-- pure pseudo-random number generator and then running it with 'runGenM'. -- --- == How to generate pseudo-random values in pure code --- --- In pure code, use 'runGenState' and its variants to extract the pure --- pseudo-random value from a monadic computation based on a pure pseudo-random --- number generator. --- --- >>> let pureGen = mkStdGen 41 --- >>> runGenState_ pureGen (rolls 10) :: [Word8] --- [6,4,5,1,1,3,2,4,5,5] +-- >>> :{ +-- let rollsM :: MonadRandom g s m => Int -> g s -> m [Word8] +-- rollsM n = replicateM n . uniformRM (1, 6) +-- pureGen = mkStdGen 42 +-- in +-- runGenM_ (IOGen pureGen) (rollsM 10) :: IO [Word8] +-- :} +-- [5,1,4,3,3,2,5,2,2,4] ------------------------------------------------------------------------------- -- Pseudo-random number generator interfaces From 4ad8a86a47dc3b93e40eea28774c0840be779de8 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 7 May 2020 14:21:49 +0200 Subject: [PATCH 162/170] More self-contained docs for uniform and uniformR --- System/Random.hs | 21 ++++++++++++++++++--- System/Random/Internal.hs | 8 ++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 721ea122a..4264638cf 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -127,14 +127,29 @@ import qualified System.Random.SplitMix as SM -- See "System.Random.Monad" module -- --- | Pure version of `uniformM` that works with instances of `RandomGen` +-- | Generates a value uniformly distributed over all possible values of that +-- type. +-- +-- This is a pure version of 'System.Random.Monad.uniformM'. -- -- @since 1.2 uniform :: (RandomGen g, Uniform a) => g -> (a, g) uniform g = runGenState g uniformM - --- | Pure version of `uniformRM` that works with instances of `RandomGen` +-- | Generates a value uniformly distributed over the provided range, which +-- is interpreted as inclusive in the lower and upper bound. +-- +-- * @uniformR (1 :: Int, 4 :: Int)@ generates values uniformly from the set +-- \(\{1,2,3,4\}\) +-- +-- * @uniformR (1 :: Float, 4 :: Float)@ generates values uniformly from the +-- set \(\{x\;|\;1 \le x \le 4\}\) +-- +-- The following law should hold to make the function always defined: +-- +-- > uniformR (a, b) = uniformR (b, a) +-- +-- This is a pure version of 'System.Random.Monad.uniformRM'. -- -- @since 1.2 uniformR :: (RandomGen g, UniformRange a) => (a, a) -> g -> (a, g) diff --git a/System/Random/Internal.hs b/System/Random/Internal.hs index 76e101660..f88b08f6d 100644 --- a/System/Random/Internal.hs +++ b/System/Random/Internal.hs @@ -439,11 +439,11 @@ class UniformRange a where -- | Generates a value uniformly distributed over the provided range, which -- is interpreted as inclusive in the lower and upper bound. -- - -- * @uniformR (1 :: Int, 4 :: Int)@ should generate values uniformly from - -- the set \(\{1,2,3,4\}\) + -- * @uniformRM (1 :: Int, 4 :: Int)@ generates values uniformly from the + -- set \(\{1,2,3,4\}\) -- - -- * @uniformR (1 :: Float, 4 :: Float)@ should generate values uniformly - -- from the set \(\{x\;|\;1 \le x \le 4\}\) + -- * @uniformRM (1 :: Float, 4 :: Float)@ generates values uniformly from + -- the set \(\{x\;|\;1 \le x \le 4\}\) -- -- The following law should hold to make the function always defined: -- From 07016de0b014d912c6e17101407920e41a421ab1 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Thu, 7 May 2020 14:24:42 +0200 Subject: [PATCH 163/170] Fix section include --- System/Random.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/System/Random.hs b/System/Random.hs index 4264638cf..9256f5575 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -79,7 +79,7 @@ import qualified System.Random.SplitMix as SM -- package. -- Programmers may, of course, supply their own instances of 'RandomGen'. -- --- $ usagepure +-- $usagepure -- -- In pure code, use 'uniform' and 'uniformR' to generate pseudo-random values -- with a pure pseudo-random number generator like 'StdGen'. From cd5421f8d702c62b5cd78272b3e1d79ce220a385 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Thu, 7 May 2020 15:28:24 +0300 Subject: [PATCH 164/170] Refactor naming of mutable generators and switch to `TypeFamilyDependencies` for `Frozen` --- System/Random.hs | 20 ++--- System/Random/Internal.hs | 58 +++++++------- System/Random/Monad.hs | 155 +++++++++++++++++++++----------------- stack-old.yaml | 2 - test/Spec/Range.hs | 4 +- test/Spec/Run.hs | 2 +- 6 files changed, 128 insertions(+), 113 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index 9256f5575..bff4145f7 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -101,7 +101,7 @@ import qualified System.Random.SplitMix as SM -- rollsM n = replicateM n . uniformRM (1, 6) -- pureGen = mkStdGen 137 -- in --- runGenState_ pureGen (rollsM 10) :: [Word8] +-- runStateGen_ pureGen (rollsM 10) :: [Word8] -- :} -- [1,2,6,6,5,1,4,6,5,4] @@ -134,7 +134,7 @@ import qualified System.Random.SplitMix as SM -- -- @since 1.2 uniform :: (RandomGen g, Uniform a) => g -> (a, g) -uniform g = runGenState g uniformM +uniform g = runStateGen g uniformM -- | Generates a value uniformly distributed over the provided range, which -- is interpreted as inclusive in the lower and upper bound. @@ -153,14 +153,14 @@ uniform g = runGenState g uniformM -- -- @since 1.2 uniformR :: (RandomGen g, UniformRange a) => (a, a) -> g -> (a, g) -uniformR r g = runGenState g (uniformRM r) +uniformR r g = runStateGen g (uniformRM r) -- | Generates a 'ByteString' of the specified size using a pure pseudo-random -- number generator. See 'uniformByteString' for the monadic version. -- -- @since 1.2 genByteString :: RandomGen g => Int -> g -> (ByteString, g) -genByteString n g = runPureGenST g (uniformByteString n) +genByteString n g = runStateGenST g (uniformByteString n) {-# INLINE genByteString #-} -- | The class of types for which uniformly distributed values can be @@ -180,7 +180,7 @@ class Random a where {-# INLINE randomR #-} randomR :: RandomGen g => (a, a) -> g -> (a, g) default randomR :: (RandomGen g, UniformRange a) => (a, a) -> g -> (a, g) - randomR r g = runGenState g (uniformRM r) + randomR r g = runStateGen g (uniformRM r) -- | The same as 'randomR', but using a default range determined by the type: -- @@ -194,7 +194,7 @@ class Random a where {-# INLINE random #-} random :: RandomGen g => g -> (a, g) default random :: (RandomGen g, Uniform a) => g -> (a, g) - random g = runGenState g uniformM + random g = runStateGen g uniformM -- | Plural variant of 'randomR', producing an infinite list of -- pseudo-random values instead of returning a new generator. @@ -264,11 +264,11 @@ instance Random CDouble where instance Random Char instance Random Bool instance Random Double where - randomR r g = runGenState g (uniformRM r) - random g = runGenState g (uniformRM (0, 1)) + randomR r g = runStateGen g (uniformRM r) + random g = runStateGen g (uniformRM (0, 1)) instance Random Float where - randomR r g = runGenState g (uniformRM r) - random g = runGenState g (uniformRM (0, 1)) + randomR r g = runStateGen g (uniformRM r) + random g = runStateGen g (uniformRM (0, 1)) ------------------------------------------------------------------------------- -- Global pseudo-random number generator diff --git a/System/Random/Internal.hs b/System/Random/Internal.hs index f88b08f6d..5ba916b8f 100644 --- a/System/Random/Internal.hs +++ b/System/Random/Internal.hs @@ -11,6 +11,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE Trustworthy #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeFamilyDependencies #-} {-# LANGUAGE UnboxedTuples #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UnliftedFFITypes #-} @@ -29,7 +30,6 @@ module System.Random.Internal (-- * Pure and monadic pseudo-random number generator interfaces RandomGen(..) , MonadRandom(..) - , Frozen(..) -- ** Standard pseudo-random number generator , StdGen @@ -37,13 +37,14 @@ module System.Random.Internal -- * Monadic adapters for pure pseudo-random number generators -- ** Pure adapter - , PureGen + , StateGen(..) + , StateGenM(..) , splitGen - , runGenState - , runGenState_ - , runGenStateT - , runGenStateT_ - , runPureGenST + , runStateGen + , runStateGen_ + , runStateGenT + , runStateGenT_ + , runStateGenST -- * Pseudo-random values of various types , Uniform(..) @@ -92,7 +93,7 @@ class RandomGen g where -- [here](https://alexey.kuleshevi.ch/blog/2019/12/21/random-benchmarks) for -- more details. It is thus deprecated. next :: g -> (Int, g) - next g = runGenState g (uniformRM (genRange g)) + next g = runStateGen g (uniformRM (genRange g)) -- | Returns a 'Word8' that is uniformly distributed over the entire 'Word8' -- range. @@ -134,14 +135,14 @@ class RandomGen g where -- -- @since 1.2 genWord32R :: Word32 -> g -> (Word32, g) - genWord32R m g = runGenState g (unbiasedWordMult32 m) + genWord32R m g = runStateGen g (unbiasedWordMult32 m) -- | @genWord64R upperBound g@ returns a 'Word64' that is uniformly -- distributed over the range @[0, upperBound]@. -- -- @since 1.2 genWord64R :: Word64 -> g -> (Word64, g) - genWord64R m g = runGenState g (unsignedBitmaskWithRejectionM uniformWord64 m) + genWord64R m g = runStateGen g (unsignedBitmaskWithRejectionM uniformWord64 m) -- | @genShortByteString n g@ returns a 'ShortByteString' of length @n@ -- filled with pseudo-random bytes. @@ -149,7 +150,7 @@ class RandomGen g where -- @since 1.2 genShortByteString :: Int -> g -> (ShortByteString, g) genShortByteString n g = - unsafePerformIO $ runGenStateT g (genShortByteStringIO n . uniformWord64) + unsafePerformIO $ runStateGenT g (genShortByteStringIO n . uniformWord64) {-# INLINE genShortByteString #-} -- | Yields the range of values returned by 'next'. @@ -179,7 +180,7 @@ class Monad m => MonadRandom g s m | g m -> s where -- 'thawGen' and 'freezeGen'. -- -- @since 1.2 - data Frozen g :: * + type Frozen g = (f :: *) | f -> g {-# MINIMAL freezeGen,thawGen,(uniformWord32|uniformWord64) #-} -- | Restores the pseudo-random number generator from its 'Frozen' @@ -339,12 +340,13 @@ uniformByteString n g = do -- generator. -- -- @since 1.2 -data PureGen g s = PureGenI +data StateGenM g s = StateGenM +newtype StateGen g = StateGen g -instance (RandomGen g, MonadState g m) => MonadRandom (PureGen g) g m where - newtype Frozen (PureGen g) = PureGen g - thawGen (PureGen g) = PureGenI <$ put g - freezeGen _ = fmap PureGen get +instance (RandomGen g, MonadState g m) => MonadRandom (StateGenM g) g m where + type Frozen (StateGenM g) = StateGen g + thawGen (StateGen g) = StateGenM <$ put g + freezeGen _ = fmap StateGen get uniformWord32R r _ = state (genWord32R r) uniformWord64R r _ = state (genWord64R r) uniformWord8 _ = state genWord8 @@ -366,39 +368,39 @@ splitGen = state split -- pseudo-random number generator. -- -- @since 1.2 -runGenState :: RandomGen g => g -> (PureGen g g -> State g a) -> (a, g) -runGenState g f = runState (f PureGenI) g +runStateGen :: RandomGen g => g -> (StateGenM g g -> State g a) -> (a, g) +runStateGen g f = runState (f StateGenM) g -- | Runs a monadic generating action in the `State` monad using a pure -- pseudo-random number generator. Returns only the resulting pseudo-random -- value. -- -- @since 1.2 -runGenState_ :: RandomGen g => g -> (PureGen g g -> State g a) -> a -runGenState_ g = fst . runGenState g +runStateGen_ :: RandomGen g => g -> (StateGenM g g -> State g a) -> a +runStateGen_ g = fst . runStateGen g -- | Runs a monadic generating action in the `StateT` monad using a pure -- pseudo-random number generator. -- -- @since 1.2 -runGenStateT :: RandomGen g => g -> (PureGen g g -> StateT g m a) -> m (a, g) -runGenStateT g f = runStateT (f PureGenI) g +runStateGenT :: RandomGen g => g -> (StateGenM g g -> StateT g m a) -> m (a, g) +runStateGenT g f = runStateT (f StateGenM) g -- | Runs a monadic generating action in the `StateT` monad using a pure -- pseudo-random number generator. Returns only the resulting pseudo-random -- value. -- -- @since 1.2 -runGenStateT_ :: (RandomGen g, Functor f) => g -> (PureGen g g -> StateT g f a) -> f a -runGenStateT_ g = fmap fst . runGenStateT g +runStateGenT_ :: (RandomGen g, Functor f) => g -> (StateGenM g g -> StateT g f a) -> f a +runStateGenT_ g = fmap fst . runStateGenT g -- | Runs a monadic generating action in the `ST` monad using a pure -- pseudo-random number generator. -- -- @since 1.2 -runPureGenST :: RandomGen g => g -> (forall s . PureGen g g -> StateT g (ST s) a) -> (a, g) -runPureGenST g action = runST $ runGenStateT g $ action -{-# INLINE runPureGenST #-} +runStateGenST :: RandomGen g => g -> (forall s . StateGenM g g -> StateT g (ST s) a) -> (a, g) +runStateGenST g action = runST $ runStateGenT g action +{-# INLINE runStateGenST #-} -- | The standard pseudo-random number generator. diff --git a/System/Random/Monad.hs b/System/Random/Monad.hs index 3c98b7db0..dc0110886 100644 --- a/System/Random/Monad.hs +++ b/System/Random/Monad.hs @@ -27,32 +27,35 @@ module System.Random.Monad -- * Pure and monadic pseudo-random number generator interfaces -- $interfaces , MonadRandom(..) - , Frozen(..) , runGenM , runGenM_ , RandomGenM(..) - , splitRandomGenM + , randomM + , randomRM + , splitGenM -- * Monadic adapters for pure pseudo-random number generators -- $monadicadapters -- ** Pure adapter - , PureGen - , splitGen - , genRandom - , runGenState - , runGenState_ - , runGenStateT - , runGenStateT_ - , runPureGenST + , StateGen(..) + , StateGenM(..) + , runStateGen + , runStateGen_ + , runStateGenT + , runStateGenT_ + , runStateGenST -- ** Mutable adapter with atomic operations - , AtomicGen + , AtomicGen(..) + , AtomicGenM(..) , applyAtomicGen -- ** Mutable adapter in 'IO' - , IOGen + , IOGen(..) + , IOGenM(..) , applyIOGen -- ** Mutable adapter in 'ST' - , STGen + , STGen(..) + , STGenM(..) , applySTGen , runSTGen , runSTGen_ @@ -92,7 +95,7 @@ import System.Random.Internal -- [Monadic pseudo-random number generators] 'MonadRandom' is an interface to -- monadic pseudo-random number generators. -- --- [Monadic adapters] 'PureGen', 'AtomicGen', 'IOGen' and 'STGen' turn a +-- [Monadic adapters] 'StateGenM', 'AtomicGenM', 'IOGenM' and 'STGenM' turn a -- 'RandomGen' instance into a 'MonadRandom' instance. -- -- [Drawing from a range] 'UniformRange' is used to generate a value of a @@ -163,23 +166,23 @@ import System.Random.Internal -- $monadicadapters -- -- Pure pseudo-random number generators can be used in monadic code via the --- adapters 'PureGen', 'AtomicGen', 'IOGen' and 'STGen'. +-- adapters 'StateGenM', 'AtomicGenM', 'IOGenM' and 'STGenM'. -- --- * 'PureGen' can be used in any state monad. With strict 'StateT' there is +-- * 'StateGenM' can be used in any state monad. With strict 'StateT' there is -- no performance overhead compared to using the 'RandomGen' instance --- directly. 'PureGen' is /not/ safe to use in the presence of exceptions +-- directly. 'StateGenM' is /not/ safe to use in the presence of exceptions -- and concurrency. -- --- * 'AtomicGen' is safe in the presence of exceptions and concurrency since +-- * 'AtomicGenM' is safe in the presence of exceptions and concurrency since -- it performs all actions atomically. -- --- * 'IOGen' is a wrapper around an 'IORef' that holds a pure generator. --- 'IOGen' is safe in the presence of exceptions, but not concurrency. +-- * 'IOGenM' is a wrapper around an 'IORef' that holds a pure generator. +-- 'IOGenM' is safe in the presence of exceptions, but not concurrency. -- --- * 'STGen' is a wrapper around an 'STRef' that holds a pure generator. --- 'STGen' is safe in the presence of exceptions, but not concurrency. +-- * 'STGenM' is a wrapper around an 'STRef' that holds a pure generator. +-- 'STGenM' is safe in the presence of exceptions, but not concurrency. --- | Interface to operations on 'RandomGen' wrappers like 'IOGen' and 'PureGen'. +-- | Interface to operations on 'RandomGen' wrappers like 'IOGenM' and 'StateGenM'. -- -- @since 1.2 class (RandomGen r, MonadRandom (g r) s m) => RandomGenM g r s m where @@ -189,25 +192,25 @@ class (RandomGen r, MonadRandom (g r) s m) => RandomGenM g r s m where -- wrapper with one of the resulting generators and returns the other. -- -- @since 1.2 -splitRandomGenM :: RandomGenM g r s m => g r s -> m r -splitRandomGenM = applyRandomGenM split +splitGenM :: RandomGenM g r s m => g r s -> m r +splitGenM = applyRandomGenM split -instance (RandomGen r, MonadIO m) => RandomGenM IOGen r RealWorld m where +instance (RandomGen r, MonadIO m) => RandomGenM IOGenM r RealWorld m where applyRandomGenM = applyIOGen -instance (RandomGen r, MonadIO m) => RandomGenM AtomicGen r RealWorld m where +instance (RandomGen r, MonadIO m) => RandomGenM AtomicGenM r RealWorld m where applyRandomGenM = applyAtomicGen -instance (RandomGen r, MonadState r m) => RandomGenM PureGen r r m where +instance (RandomGen r, MonadState r m) => RandomGenM StateGenM r r m where applyRandomGenM f _ = state f -instance RandomGen r => RandomGenM STGen r s (ST s) where +instance RandomGen r => RandomGenM STGenM r s (ST s) where applyRandomGenM = applySTGen -- | Runs a mutable pseudo-random number generator from its 'Frozen' state. -- -- >>> import Data.Int (Int8) --- >>> runGenM (IOGen (mkStdGen 217)) (`uniformListM` 5) :: IO ([Int8], Frozen (IOGen StdGen)) +-- >>> runGenM (IOGen (mkStdGen 217)) (`uniformListM` 5) :: IO ([Int8], IOGen StdGen) -- ([-74,37,-50,-2,3],IOGen {unIOGen = SMGen 4273268533320920145 15251669095119325999}) -- -- @since 1.2 @@ -230,27 +233,35 @@ runGenM_ fg action = fst <$> runGenM fg action uniformListM :: (MonadRandom g s m, Uniform a) => g s -> Int -> m [a] uniformListM gen n = replicateM n (uniformM gen) --- | Generates a pseudo-random value in a state monad. +-- | Generates a pseudo-random value using monadic interface and `Random` instance. -- -- @since 1.2 -genRandom :: (RandomGen g, Random a, MonadState g m) => PureGen g g -> m a -genRandom _ = state random +randomM :: (RandomGenM g r s m, Random a) => g r s -> m a +randomM = applyRandomGenM random + +-- | Generates a pseudo-random value using monadic interface and `Random` instance. +-- +-- @since 1.2 +randomRM :: (RandomGenM g r s m, Random a) => (a, a) -> g r s -> m a +randomRM r = applyRandomGenM (randomR r) -- | Wraps an 'IORef' that holds a pure pseudo-random number generator. All -- operations are performed atomically. -- --- * 'AtomicGen' is safe in the presence of exceptions and concurrency. --- * 'AtomicGen' is the slowest of the monadic adapters due to the overhead +-- * 'AtomicGenM' is safe in the presence of exceptions and concurrency. +-- * 'AtomicGenM' is the slowest of the monadic adapters due to the overhead -- of its atomic operations. -- -- @since 1.2 -newtype AtomicGen g s = AtomicGenI (IORef g) +newtype AtomicGenM g s = AtomicGenM { unAtomicGenM :: IORef g} -instance (RandomGen g, MonadIO m) => MonadRandom (AtomicGen g) RealWorld m where - newtype Frozen (AtomicGen g) = AtomicGen { unAtomicGen :: g } +newtype AtomicGen g = AtomicGen { unAtomicGen :: g } deriving (Eq, Show, Read) - thawGen (AtomicGen g) = fmap AtomicGenI (liftIO $ newIORef g) - freezeGen (AtomicGenI gVar) = fmap AtomicGen (liftIO $ readIORef gVar) + +instance (RandomGen g, MonadIO m) => MonadRandom (AtomicGenM g) RealWorld m where + type Frozen (AtomicGenM g) = AtomicGen g + thawGen (AtomicGen g) = fmap AtomicGenM (liftIO $ newIORef g) + freezeGen (AtomicGenM gVar) = fmap AtomicGen (liftIO $ readIORef gVar) uniformWord32R r = applyAtomicGen (genWord32R r) {-# INLINE uniformWord32R #-} uniformWord64R r = applyAtomicGen (genWord64R r) @@ -269,8 +280,8 @@ instance (RandomGen g, MonadIO m) => MonadRandom (AtomicGen g) RealWorld m where -- generator. -- -- @since 1.2 -applyAtomicGen :: MonadIO m => (g -> (a, g)) -> AtomicGen g RealWorld -> m a -applyAtomicGen op (AtomicGenI gVar) = +applyAtomicGen :: MonadIO m => (g -> (a, g)) -> AtomicGenM g RealWorld -> m a +applyAtomicGen op (AtomicGenM gVar) = liftIO $ atomicModifyIORef' gVar $ \g -> case op g of (a, g') -> (g', a) @@ -278,10 +289,10 @@ applyAtomicGen op (AtomicGenI gVar) = -- | Wraps an 'IORef' that holds a pure pseudo-random number generator. -- --- * 'IOGen' is safe in the presence of exceptions, but not concurrency. --- * 'IOGen' is slower than 'PureGen' due to the extra pointer indirection. --- * 'IOGen' is faster than 'AtomicGen' since the 'IORef' operations used by --- 'IOGen' are not atomic. +-- * 'IOGenM' is safe in the presence of exceptions, but not concurrency. +-- * 'IOGenM' is slower than 'StateGenM' due to the extra pointer indirection. +-- * 'IOGenM' is faster than 'AtomicGenM' since the 'IORef' operations used by +-- 'IOGenM' are not atomic. -- -- An example use case is writing pseudo-random bytes into a file: -- @@ -294,13 +305,15 @@ applyAtomicGen op (AtomicGenI gVar) = -- >>> runGenM_ (IOGen (mkStdGen 1729)) ioGen -- -- @since 1.2 -newtype IOGen g s = IOGenI (IORef g) +newtype IOGenM g s = IOGenM { unIOGenM :: IORef g } -instance (RandomGen g, MonadIO m) => MonadRandom (IOGen g) RealWorld m where - newtype Frozen (IOGen g) = IOGen { unIOGen :: g } +newtype IOGen g = IOGen { unIOGen :: g } deriving (Eq, Show, Read) - thawGen (IOGen g) = fmap IOGenI (liftIO $ newIORef g) - freezeGen (IOGenI gVar) = fmap IOGen (liftIO $ readIORef gVar) + +instance (RandomGen g, MonadIO m) => MonadRandom (IOGenM g) RealWorld m where + type Frozen (IOGenM g) = IOGen g + thawGen (IOGen g) = fmap IOGenM (liftIO $ newIORef g) + freezeGen (IOGenM gVar) = fmap IOGen (liftIO $ readIORef gVar) uniformWord32R r = applyIOGen (genWord32R r) {-# INLINE uniformWord32R #-} uniformWord64R r = applyIOGen (genWord64R r) @@ -318,8 +331,8 @@ instance (RandomGen g, MonadIO m) => MonadRandom (IOGen g) RealWorld m where -- | Applies a pure operation to the wrapped pseudo-random number generator. -- -- @since 1.2 -applyIOGen :: MonadIO m => (g -> (a, g)) -> IOGen g RealWorld -> m a -applyIOGen f (IOGenI ref) = liftIO $ do +applyIOGen :: MonadIO m => (g -> (a, g)) -> IOGenM g RealWorld -> m a +applyIOGen f (IOGenM ref) = liftIO $ do g <- readIORef ref case f g of (!a, !g') -> a <$ writeIORef ref g' @@ -327,17 +340,19 @@ applyIOGen f (IOGenI ref) = liftIO $ do -- | Wraps an 'STRef' that holds a pure pseudo-random number generator. -- --- * 'STGen' is safe in the presence of exceptions, but not concurrency. --- * 'STGen' is slower than 'PureGen' due to the extra pointer indirection. +-- * 'STGenM' is safe in the presence of exceptions, but not concurrency. +-- * 'STGenM' is slower than 'StateGenM' due to the extra pointer indirection. -- -- @since 1.2 -newtype STGen g s = STGenI (STRef s g) +newtype STGenM g s = STGenM { unSTGenM :: STRef s g } -instance RandomGen g => MonadRandom (STGen g) s (ST s) where - newtype Frozen (STGen g) = STGen { unSTGen :: g } +newtype STGen g = STGen { unSTGen :: g } deriving (Eq, Show, Read) - thawGen (STGen g) = fmap STGenI (newSTRef g) - freezeGen (STGenI gVar) = fmap STGen (readSTRef gVar) + +instance RandomGen g => MonadRandom (STGenM g) s (ST s) where + type Frozen (STGenM g) = STGen g + thawGen (STGen g) = fmap STGenM (newSTRef g) + freezeGen (STGenM gVar) = fmap STGen (readSTRef gVar) uniformWord32R r = applySTGen (genWord32R r) {-# INLINE uniformWord32R #-} uniformWord64R r = applySTGen (genWord64R r) @@ -355,8 +370,8 @@ instance RandomGen g => MonadRandom (STGen g) s (ST s) where -- | Applies a pure operation to the wrapped pseudo-random number generator. -- -- @since 1.2 -applySTGen :: (g -> (a, g)) -> STGen g s -> ST s a -applySTGen f (STGenI ref) = do +applySTGen :: (g -> (a, g)) -> STGenM g s -> ST s a +applySTGen f (STGenM ref) = do g <- readSTRef ref case f g of (!a, !g') -> a <$ writeSTRef ref g' @@ -366,7 +381,7 @@ applySTGen f (STGenI ref) = do -- pseudo-random number generator. -- -- @since 1.2 -runSTGen :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> (a, g) +runSTGen :: RandomGen g => g -> (forall s . STGenM g s -> ST s a) -> (a, g) runSTGen g action = unSTGen <$> runST (runGenM (STGen g) action) -- | Runs a monadic generating action in the `ST` monad using a pure @@ -374,7 +389,7 @@ runSTGen g action = unSTGen <$> runST (runGenM (STGen g) action) -- value. -- -- @since 1.2 -runSTGen_ :: RandomGen g => g -> (forall s . STGen g s -> ST s a) -> a +runSTGen_ :: RandomGen g => g -> (forall s . STGenM g s -> ST s a) -> a runSTGen_ g action = fst $ runSTGen g action @@ -420,9 +435,9 @@ runSTGen_ g action = fst $ runSTGen g action -- from the @mwc-random@ package: -- -- > instance (s ~ PrimState m, PrimMonad m) => MonadRandom MWC.Gen s m where --- > newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } --- > thawGen = fmap MWC.restore unFrozen --- > freezeGen = fmap Frozen . MWC.save +-- > type Frozen MWC.Gen = MWC.Seed +-- > thawGen = MWC.restore +-- > freezeGen = MWC.save -- > uniformWord8 = MWC.uniform -- > uniformWord16 = MWC.uniform -- > uniformWord32 = MWC.uniform @@ -456,9 +471,9 @@ runSTGen_ g action = fst $ runSTGen g action -- -- >>> :{ -- instance (s ~ PrimState m, PrimMonad m) => MonadRandom MWC.Gen s m where --- newtype Frozen MWC.Gen = Frozen { unFrozen :: MWC.Seed } --- thawGen = fmap MWC.restore unFrozen --- freezeGen = fmap Frozen . MWC.save +-- type Frozen MWC.Gen = MWC.Seed +-- thawGen = MWC.restore +-- freezeGen = MWC.save -- uniformWord8 = MWC.uniform -- uniformWord16 = MWC.uniform -- uniformWord32 = MWC.uniform diff --git a/stack-old.yaml b/stack-old.yaml index 750067a74..87c4784a5 100644 --- a/stack-old.yaml +++ b/stack-old.yaml @@ -51,10 +51,8 @@ extra-deps: # implement 'Prim'. So far, this is only the case on lehin's fork, so we use # that. - splitmix-0.0.4@sha256:fb9bb8b54a2e76c8a021fe5c4c3798047e1f60e168379a1f80693047fe00ad0e,4813 - # Not contained in all snapshots we want to test against - doctest-0.16.2@sha256:2f96e9bbe9aee11b47453c82c24b3dc76cdbb8a2a7c984dfd60b4906d08adf68,6942 - cabal-doctest-1.0.8@sha256:34dff6369d417df2699af4e15f06bc181d495eca9c51efde173deae2053c197c,1491 -- primitive-0.6.4.0@sha256:5b6a2c3cc70a35aabd4565fcb9bb1dd78fe2814a36e62428a9a1aae8c32441a1,2079 # Override default flag values for local packages and extra-deps flags: diff --git a/test/Spec/Range.hs b/test/Spec/Range.hs index a48ac2b4a..d9e8c0a95 100644 --- a/test/Spec/Range.hs +++ b/test/Spec/Range.hs @@ -25,10 +25,10 @@ singleton g x = result == x uniformRangeWithin :: (RandomGen g, UniformRange a, Ord a) => g -> (a, a) -> Bool uniformRangeWithin gen (l, r) = - runGenState_ gen $ \g -> + runStateGen_ gen $ \g -> (\result -> min l r <= result && result <= max l r) <$> uniformRM (l, r) g uniformRangeWithinExcluded :: (RandomGen g, UniformRange a, Ord a) => g -> (a, a) -> Bool uniformRangeWithinExcluded gen (l, r) = - runGenState_ gen $ \g -> + runStateGen_ gen $ \g -> (\result -> min l r <= result && (l == r || result < max l r)) <$> uniformRM (l, r) g diff --git a/test/Spec/Run.hs b/test/Spec/Run.hs index b0e6565d5..c0e00edb8 100644 --- a/test/Spec/Run.hs +++ b/test/Spec/Run.hs @@ -5,7 +5,7 @@ import System.Random.Monad runsEqual :: RandomGen g => g -> IO Bool runsEqual g = do - let pureResult = runGenState_ g uniformM :: Word64 + let pureResult = runStateGen_ g uniformM :: Word64 stResult = runSTGen_ g uniformM ioResult <- runGenM_ (IOGen g) uniformM atomicResult <- runGenM_ (AtomicGen g) uniformM From 9ee79a772f4c71492c411cc56577c9d7be18adfb Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Fri, 8 May 2020 15:40:18 +0200 Subject: [PATCH 165/170] StdGen: constructor accessible via Internal only (#123) Fixes https://github.com/haskell/random/issues/59 by making 'StdGen' not an instance of 'Read'. --- System/Random.hs | 2 +- System/Random/Internal.hs | 10 ++++++---- System/Random/Monad.hs | 2 +- bench/Main.hs | 8 ++++---- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/System/Random.hs b/System/Random.hs index bff4145f7..120dd37da 100644 --- a/System/Random.hs +++ b/System/Random.hs @@ -293,7 +293,7 @@ getStdGen :: MonadIO m => m StdGen getStdGen = liftIO $ readIORef theStdGen theStdGen :: IORef StdGen -theStdGen = unsafePerformIO $ SM.initSMGen >>= newIORef +theStdGen = unsafePerformIO $ SM.initSMGen >>= newIORef . StdGen {-# NOINLINE theStdGen #-} -- |Applies 'split' to the current global pseudo-random generator, diff --git a/System/Random/Internal.hs b/System/Random/Internal.hs index 5ba916b8f..7ec513ad8 100644 --- a/System/Random/Internal.hs +++ b/System/Random/Internal.hs @@ -5,6 +5,7 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GHCForeignImportPrim #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} @@ -32,7 +33,7 @@ module System.Random.Internal , MonadRandom(..) -- ** Standard pseudo-random number generator - , StdGen + , StdGen(..) , mkStdGen -- * Monadic adapters for pure pseudo-random number generators @@ -404,9 +405,10 @@ runStateGenST g action = runST $ runStateGenT g action -- | The standard pseudo-random number generator. -type StdGen = SM.SMGen +newtype StdGen = StdGen { unStdGen :: SM.SMGen } + deriving (RandomGen, Show) -instance RandomGen StdGen where +instance RandomGen SM.SMGen where next = SM.nextInt genWord32 = SM.nextWord32 genWord64 = SM.nextWord64 @@ -420,7 +422,7 @@ instance RandomGen SM32.SMGen where -- | Constructs a 'StdGen' deterministically. mkStdGen :: Int -> StdGen -mkStdGen s = SM.mkSMGen $ fromIntegral s +mkStdGen = StdGen . SM.mkSMGen . fromIntegral -- | The class of types for which a uniformly distributed value can be drawn -- from all possible values of the type. diff --git a/System/Random/Monad.hs b/System/Random/Monad.hs index dc0110886..585023775 100644 --- a/System/Random/Monad.hs +++ b/System/Random/Monad.hs @@ -211,7 +211,7 @@ instance RandomGen r => RandomGenM STGenM r s (ST s) where -- -- >>> import Data.Int (Int8) -- >>> runGenM (IOGen (mkStdGen 217)) (`uniformListM` 5) :: IO ([Int8], IOGen StdGen) --- ([-74,37,-50,-2,3],IOGen {unIOGen = SMGen 4273268533320920145 15251669095119325999}) +-- ([-74,37,-50,-2,3],IOGen {unIOGen = StdGen {unStdGen = SMGen 4273268533320920145 15251669095119325999}}) -- -- @since 1.2 runGenM :: MonadRandom g s m => Frozen g -> (g s -> m a) -> m (a, Frozen g) diff --git a/bench/Main.hs b/bench/Main.hs index 0f1b8eaf4..0b6a6a537 100644 --- a/bench/Main.hs +++ b/bench/Main.hs @@ -22,10 +22,10 @@ main = do let !sz = 100000 defaultMain [ bgroup "baseline" - [ let !stdGen = mkStdGen 1337 in bench "nextWord32" $ nf (genMany SM.nextWord32 stdGen) sz - , let !stdGen = mkStdGen 1337 in bench "nextWord64" $ nf (genMany SM.nextWord64 stdGen) sz - , let !stdGen = mkStdGen 1337 in bench "nextInt" $ nf (genMany SM.nextInt stdGen) sz - , let !stdGen = mkStdGen 1337 in bench "split" $ nf (genMany SM.splitSMGen stdGen) sz + [ let !smGen = SM.mkSMGen 1337 in bench "nextWord32" $ nf (genMany SM.nextWord32 smGen) sz + , let !smGen = SM.mkSMGen 1337 in bench "nextWord64" $ nf (genMany SM.nextWord64 smGen) sz + , let !smGen = SM.mkSMGen 1337 in bench "nextInt" $ nf (genMany SM.nextInt smGen) sz + , let !smGen = SM.mkSMGen 1337 in bench "split" $ nf (genMany SM.splitSMGen smGen) sz ] , bgroup "pure" [ bgroup "random" From eab4f5ad89a1c676dde1a89022e07ccec057d9e4 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Thu, 7 May 2020 16:34:42 +0100 Subject: [PATCH 166/170] Ready for review --- CHANGELOG.md | 87 ++++++++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a3577c4e..f3f1c93ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ bumped version for float/double range bugfix 2. Support for monadic generators e.g. [mwc-random](https://hackage.haskell.org/package/mwc-random). 3. Monadic adapters for pure generators (providing a uniform monadic interface to pure and monadic generators). -4. Faster by more x10 (depending on the type) - see below for benchmarks. +4. Faster by more x1000 (depending on the type) - see below for benchmarks. 5. Passes a large number of random number test suites: * [dieharder](http://webhome.phy.duke.edu/~rgb/General/dieharder.php "venerable") * [TestU01 (SmallCrush, Crush, BigCrush)](http://simul.iro.umontreal.ca/testu01/tu01.html "venerable") @@ -49,46 +49,47 @@ bumped version for float/double range bugfix ## Benchmarks -### Notes - -1. These are **not** percentage (%) increases. Random `Int`s are produced 48.9 times faster! -2. The only type for which generation is slower is for `Integer`s (on - ranges); in the version 1.1 the generation for `Integer` was - biased. - -### Without Specifying Ranges - - |----------|----------------|----------------|----------------------| - | Type | Cycles/Int 1.1 | Cycles/Int 1.2 | Performance Increase | - |----------|----------------|----------------|----------------------| - | Ints | 1508 | 30.84 | 48.9 | - | Word16 | 495 | 30.88 | 16.0 | - | Floats | 1036 | 35.11 | 29.5 | - | CFloats | 1054 | 33.75 | 31.2 | - | Doubles | 1875 | 35.77 | 52.4 | - | CDoubles | 908 | 33.31 | 27.3 | - | Integers | 1578 | 33.09 | 47.7 | - | Bools | 698 | 36.15 | 19.3 | - | Chars | 693 | 57.6 | 12.0 | - |----------|----------------|----------------|----------------------| - -### Specifying Ranges - - |--------------|----------------|----------------|----------------------| - | Type | Cycles/Int 1.1 | Cycles/Int 1.2 | Performance Increase | - |--------------|----------------|----------------|----------------------| - | Ints | 734 | 102 | 7.2 | - | Word16s | 748 | 115 | 6.5 | - | Floats | 2055 | 35.88 | 57.3 | - | CFloats | 1071 | 34.96 | 30.6 | - | Doubles | 3050 | 35.89 | 85.0 | - | CDoubles | 1112 | 34.87 | 31.9 | - | Integers | 534 | 868 | 0.6 | - | Bools | 739 | 35.22 | 21.0 | - | Chars | 790 | 133 | 5.9 | - | BIG Integers | 199848 | 103056 | 1.9 | - |--------------|----------------|----------------|----------------------| - - - +Here are some benchmarks run on a 3.1 GHz Intel Core i7. The full +benchmarks can be run using e.g. `stack bench`. The benchmarks are +measured in milliseconds per 100,000 generations. In some cases, the +performance is over x1000 times better. + + |------------|----------|----------| + | Name | 1.1 Mean | 1.2 Mean | + |------------|----------|----------| + | Float | 27.819 | 0.305 | + | Double | 50.644 | 0.328 | + | Integer | 42.332 | 0.332 | + | Word8 | 12.591 | 0.028 | + | Word16 | 12.726 | 0.028 | + | Word32 | 20.429 | 0.027 | + | Word64 | 42.299 | 0.028 | + | Word | 40.739 | 0.027 | + | Int8 | 13.479 | 0.027 | + | Int16 | 13.218 | 0.027 | + | Int32 | 20.562 | 0.027 | + | Int64 | 46.513 | 0.029 | + | Int | 43.847 | 0.028 | + | Char | 17.009 | 0.462 | + | Bool | 17.542 | 0.027 | + | CChar | 13.276 | 0.027 | + | CSChar | 13.287 | 0.027 | + | CUChar | 13.409 | 0.027 | + | CShort | 13.158 | 0.027 | + | CUShort | 12.865 | 0.027 | + | CInt | 20.705 | 0.028 | + | CUInt | 19.895 | 0.027 | + | CLong | 41.679 | 0.027 | + | CULong | 40.806 | 0.027 | + | CPtrdiff | 41.878 | 0.027 | + | CSize | 40.739 | 0.027 | + | CWchar | 20.718 | 0.027 | + | CSigAtomic | 20.768 | 0.029 | + | CLLong | 42.011 | 0.028 | + | CULLong | 41.428 | 0.027 | + | CIntPtr | 45.385 | 0.027 | + | CUIntPtr | 40.797 | 0.027 | + | CIntMax | 41.778 | 0.027 | + | CUIntMax | 40.467 | 0.027 | + |------------|----------|----------| From d27ed91733fb17470c00478b037cdff2d75c18af Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Sun, 10 May 2020 12:37:59 +0100 Subject: [PATCH 167/170] Incorporate feedback --- CHANGELOG.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3f1c93ac..21d974bc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,9 @@ bumped version for float/double range bugfix 2. Support for monadic generators e.g. [mwc-random](https://hackage.haskell.org/package/mwc-random). 3. Monadic adapters for pure generators (providing a uniform monadic interface to pure and monadic generators). -4. Faster by more x1000 (depending on the type) - see below for benchmarks. +4. Faster in all cases bar one by more than x18 (N.B. x18 not 18%) and + some cases (depending on the type) faster by more than x1000 - see + below for benchmarks. 5. Passes a large number of random number test suites: * [dieharder](http://webhome.phy.duke.edu/~rgb/General/dieharder.php "venerable") * [TestU01 (SmallCrush, Crush, BigCrush)](http://simul.iro.umontreal.ca/testu01/tu01.html "venerable") @@ -52,11 +54,12 @@ bumped version for float/double range bugfix Here are some benchmarks run on a 3.1 GHz Intel Core i7. The full benchmarks can be run using e.g. `stack bench`. The benchmarks are measured in milliseconds per 100,000 generations. In some cases, the -performance is over x1000 times better. +performance is over x1000 times better; the minimum performance +increase for the types listed below is more than x36. - |------------|----------|----------| + |------------+----------+----------| | Name | 1.1 Mean | 1.2 Mean | - |------------|----------|----------| + |------------+----------+----------| | Float | 27.819 | 0.305 | | Double | 50.644 | 0.328 | | Integer | 42.332 | 0.332 | @@ -91,5 +94,5 @@ performance is over x1000 times better. | CUIntPtr | 40.797 | 0.027 | | CIntMax | 41.778 | 0.027 | | CUIntMax | 40.467 | 0.027 | - |------------|----------|----------| + |------------+----------+----------| From 6969cbbef186ea422430a5981a505afc8ab729e1 Mon Sep 17 00:00:00 2001 From: Dominic Steinitz Date: Mon, 11 May 2020 15:24:16 +0100 Subject: [PATCH 168/170] Replace dialect and order traditionally --- CHANGELOG.md | 53 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d974bc5..76b96611e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,36 +1,10 @@ -# 1.1 - * breaking change to `randomIValInteger` to improve RNG quality and performance - see https://github.com/haskell/random/pull/4 and - ghc https://ghc.haskell.org/trac/ghc/ticket/8898 - * correct documentation about generated range of Int32 sized values of type Int - https://github.com/haskell/random/pull/7 - * fix memory leaks by using strict fields and strict atomicModifyIORef' - https://github.com/haskell/random/pull/8 - related to ghc trac tickets #7936 and #4218 - * support for base < 4.6 (which doesnt provide strict atomicModifyIORef') - and integrating Travis CI support. - https://github.com/haskell/random/pull/12 - * fix C type in test suite https://github.com/haskell/random/pull/9 - -# 1.0.1.1 -bump for overflow bug fixes - -# 1.0.1.2 -bump for ticket 8704, build fusion - -# 1.0.1.0 -bump for bug fixes, - -# 1.0.0.4 -bumped version for float/double range bugfix - # 1.2 1. Breaking change which mostly maintains backwards compatibility. 2. Support for monadic generators e.g. [mwc-random](https://hackage.haskell.org/package/mwc-random). 3. Monadic adapters for pure generators (providing a uniform monadic interface to pure and monadic generators). -4. Faster in all cases bar one by more than x18 (N.B. x18 not 18%) and +4. Faster in all cases except one by more than x18 (N.B. x18 not 18%) and some cases (depending on the type) faster by more than x1000 - see below for benchmarks. 5. Passes a large number of random number test suites: @@ -96,3 +70,28 @@ increase for the types listed below is more than x36. | CUIntMax | 40.467 | 0.027 | |------------+----------+----------| +# 1.1 + * breaking change to `randomIValInteger` to improve RNG quality and performance + see https://github.com/haskell/random/pull/4 and + ghc https://ghc.haskell.org/trac/ghc/ticket/8898 + * correct documentation about generated range of Int32 sized values of type Int + https://github.com/haskell/random/pull/7 + * fix memory leaks by using strict fields and strict atomicModifyIORef' + https://github.com/haskell/random/pull/8 + related to ghc trac tickets #7936 and #4218 + * support for base < 4.6 (which doesnt provide strict atomicModifyIORef') + and integrating Travis CI support. + https://github.com/haskell/random/pull/12 + * fix C type in test suite https://github.com/haskell/random/pull/9 + +# 1.0.1.1 +bump for overflow bug fixes + +# 1.0.1.2 +bump for ticket 8704, build fusion + +# 1.0.1.0 +bump for bug fixes, + +# 1.0.0.4 +bumped version for float/double range bugfix From 1b07f2986c21e4d4b0cd3d2cc4ef12e30fcfbd58 Mon Sep 17 00:00:00 2001 From: Leonhard Markert Date: Mon, 11 May 2020 18:25:23 +0200 Subject: [PATCH 169/170] Add 'instance UniformRange Natural' (#126) --- System/Random/Internal.hs | 56 +++++++++++++++++++++------------------ bench/Main.hs | 4 +++ test/Spec.hs | 14 +++++----- test/Spec/Range.hs | 12 ++++----- 4 files changed, 48 insertions(+), 38 deletions(-) diff --git a/System/Random/Internal.hs b/System/Random/Internal.hs index 7ec513ad8..d965f33c2 100644 --- a/System/Random/Internal.hs +++ b/System/Random/Internal.hs @@ -75,11 +75,12 @@ import Foreign.Ptr (plusPtr) import Foreign.Storable (peekByteOff, pokeByteOff) import GHC.Exts import GHC.ForeignPtr +import GHC.IO (IO(..)) +import GHC.Word +import Numeric.Natural (Natural) import System.IO.Unsafe (unsafePerformIO) import qualified System.Random.SplitMix as SM import qualified System.Random.SplitMix32 as SM32 -import GHC.Word -import GHC.IO (IO(..)) -- | 'RandomGen' is an interface to pure pseudo-random number generators. -- @@ -457,7 +458,10 @@ class UniformRange a where uniformRM :: MonadRandom g s m => (a, a) -> g s -> m a instance UniformRange Integer where - uniformRM = uniformIntegerM + uniformRM = uniformIntegralM + +instance UniformRange Natural where + uniformRM = uniformIntegralM instance Uniform Int8 where uniformM = fmap (fromIntegral :: Word8 -> Int8) . uniformWord8 @@ -751,25 +755,25 @@ randomIvalInteger (l,h) rng (x,g') = next g v' = (v * b + (fromIntegral x - fromIntegral genlo)) --- | Generate an 'Integer' in the range @[l, h]@ if @l <= h@ and @[h, l]@ +-- | Generate an integral in the range @[l, h]@ if @l <= h@ and @[h, l]@ -- otherwise. -uniformIntegerM :: (MonadRandom g s m) => (Integer, Integer) -> g s -> m Integer -uniformIntegerM (l, h) gen = case l `compare` h of +uniformIntegralM :: (Bits a, Integral a, MonadRandom g s m) => (a, a) -> g s -> m a +uniformIntegralM (l, h) gen = case l `compare` h of LT -> do let limit = h - l let limitAsWord64 :: Word64 = fromIntegral limit bounded <- - if (toInteger limitAsWord64) == limit + if (fromIntegral limitAsWord64) == limit -- Optimisation: if 'limit' fits into 'Word64', generate a bounded -- 'Word64' and then convert to 'Integer' - then toInteger <$> unsignedBitmaskWithRejectionM uniformWord64 limitAsWord64 gen - else boundedExclusiveIntegerM (limit + 1) gen + then fromIntegral <$> unsignedBitmaskWithRejectionM uniformWord64 limitAsWord64 gen + else boundedExclusiveIntegralM (limit + 1) gen return $ l + bounded - GT -> uniformIntegerM (h, l) gen + GT -> uniformIntegralM (h, l) gen EQ -> pure l -{-# INLINE uniformIntegerM #-} +{-# INLINEABLE uniformIntegralM #-} --- | Generate an 'Integer' in the range @[0, s)@ using a variant of Lemire's +-- | Generate an integral in the range @[0, s)@ using a variant of Lemire's -- multiplication method. -- -- Daniel Lemire. 2019. Fast Random Integer Generation in an Interval. In ACM @@ -777,19 +781,19 @@ uniformIntegerM (l, h) gen = case l `compare` h of -- https://doi.org/10.1145/3230636 -- -- PRECONDITION (unchecked): s > 0 -boundedExclusiveIntegerM :: (MonadRandom g s m) => Integer -> g s -> m Integer -boundedExclusiveIntegerM s gen = go +boundedExclusiveIntegralM :: (Bits a, Integral a, Ord a, MonadRandom g s m) => a -> g s -> m a +boundedExclusiveIntegralM (s :: a) gen = go where - n = integerWordSize s + n = integralWordSize s -- We renamed 'L' from the paper to 'k' here because 'L' is not a valid -- variable name in Haskell and 'l' is already used in the algorithm. k = WORD_SIZE_IN_BITS * n - twoToK = (1::Integer) `shiftL` k + twoToK = (1::a) `shiftL` k modTwoToKMask = twoToK - 1 t = (twoToK - s) `mod` s go = do - x <- uniformIntegerWords n gen + x <- uniformIntegralWords n gen let m = x * s -- m .&. modTwoToKMask == m `mod` twoToK let l = m .&. modTwoToKMask @@ -797,29 +801,29 @@ boundedExclusiveIntegerM s gen = go then go -- m `shiftR` k == m `quot` twoToK else return $ m `shiftR` k -{-# INLINE boundedExclusiveIntegerM #-} +{-# INLINE boundedExclusiveIntegralM #-} --- | @integerWordSize i@ returns that least @w@ such that +-- | @integralWordSize i@ returns that least @w@ such that -- @i <= WORD_SIZE_IN_BITS^w@. -integerWordSize :: Integer -> Int -integerWordSize = go 0 +integralWordSize :: (Bits a, Num a) => a -> Int +integralWordSize = go 0 where go !acc i | i == 0 = acc | otherwise = go (acc + 1) (i `shiftR` WORD_SIZE_IN_BITS) -{-# INLINE integerWordSize #-} +{-# INLINE integralWordSize #-} --- | @uniformIntegerWords n@ is a uniformly pseudo-random 'Integer' in the range +-- | @uniformIntegralWords n@ is a uniformly pseudo-random integral in the range -- @[0, WORD_SIZE_IN_BITS^n)@. -uniformIntegerWords :: (MonadRandom g s m) => Int -> g s -> m Integer -uniformIntegerWords n gen = go 0 n +uniformIntegralWords :: (Bits a, Integral a, MonadRandom g s m) => Int -> g s -> m a +uniformIntegralWords n gen = go 0 n where go !acc i | i == 0 = return acc | otherwise = do (w :: Word) <- uniformM gen go ((acc `shiftL` WORD_SIZE_IN_BITS) .|. (fromIntegral w)) (i - 1) -{-# INLINE uniformIntegerWords #-} +{-# INLINE uniformIntegralWords #-} -- | Uniformly generate an 'Integral' in an inclusive-inclusive range. -- diff --git a/bench/Main.hs b/bench/Main.hs index 0b6a6a537..bb0991be9 100644 --- a/bench/Main.hs +++ b/bench/Main.hs @@ -13,6 +13,7 @@ import Data.Typeable import Data.Word import Foreign.C.Types import Gauge.Main +import Numeric.Natural (Natural) import System.Random.SplitMix as SM import System.Random @@ -176,6 +177,9 @@ main = do , let !i = (10 :: Integer) ^ (100 :: Integer) !range = (-i - 1, i + 1) in pureUniformRBench @Integer range sz + , let !n = (10 :: Natural) ^ (100 :: Natural) + !range = (1, n - 1) + in pureUniformRBench @Natural range sz ] ] ] diff --git a/test/Spec.hs b/test/Spec.hs index 97aec11f6..81b469462 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -9,15 +9,16 @@ module Main (main) where import Data.Coerce -import Data.Word import Data.Int +import Data.Typeable +import Data.Word +import Foreign.C.Types +import Numeric.Natural (Natural) import System.Random +import Test.SmallCheck.Series as SC import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.SmallCheck as SC -import Test.SmallCheck.Series as SC -import Data.Typeable -import Foreign.C.Types #include "HsBaseConfig.h" @@ -66,6 +67,7 @@ main = , integralSpec @CIntMax , integralSpec @CUIntMax , integralSpec @Integer + , integralSpec @Natural -- , bitmaskSpec @Word8 -- , bitmaskSpec @Word16 -- , bitmaskSpec @Word32 @@ -103,7 +105,7 @@ showsType = showsTypeRep (typeRep (Proxy :: Proxy t)) rangeSpec :: forall a. - (SC.Serial IO a, Typeable a, Ord a, Random a, UniformRange a, Show a) + (SC.Serial IO a, Typeable a, Ord a, UniformRange a, Show a) => TestTree rangeSpec = testGroup ("Range (" ++ showsType @a ")") @@ -112,7 +114,7 @@ rangeSpec = integralSpec :: forall a. - (SC.Serial IO a, Typeable a, Ord a, Random a, UniformRange a, Show a) + (SC.Serial IO a, Typeable a, Ord a, UniformRange a, Show a) => TestTree integralSpec = testGroup ("(" ++ showsType @a ")") diff --git a/test/Spec/Range.hs b/test/Spec/Range.hs index d9e8c0a95..ef0d346ff 100644 --- a/test/Spec/Range.hs +++ b/test/Spec/Range.hs @@ -8,20 +8,20 @@ module Spec.Range import System.Random.Monad -symmetric :: (RandomGen g, Random a, Eq a) => g -> (a, a) -> Bool -symmetric g (l, r) = fst (randomR (l, r) g) == fst (randomR (r, l) g) +symmetric :: (RandomGen g, UniformRange a, Eq a) => g -> (a, a) -> Bool +symmetric g (l, r) = fst (uniformR (l, r) g) == fst (uniformR (r, l) g) -bounded :: (RandomGen g, Random a, Ord a) => g -> (a, a) -> Bool +bounded :: (RandomGen g, UniformRange a, Ord a) => g -> (a, a) -> Bool bounded g (l, r) = bottom <= result && result <= top where bottom = min l r top = max l r - result = fst (randomR (l, r) g) + result = fst (uniformR (l, r) g) -singleton :: (RandomGen g, Random a, Eq a) => g -> a -> Bool +singleton :: (RandomGen g, UniformRange a, Eq a) => g -> a -> Bool singleton g x = result == x where - result = fst (randomR (x, x) g) + result = fst (uniformR (x, x) g) uniformRangeWithin :: (RandomGen g, UniformRange a, Ord a) => g -> (a, a) -> Bool uniformRangeWithin gen (l, r) = From 73f1f6ebff0484c78de5723936efcceba102b0a8 Mon Sep 17 00:00:00 2001 From: Alexey Kuleshevich Date: Wed, 13 May 2020 14:41:36 +0300 Subject: [PATCH 170/170] Slight improvement in performance. (#125) * Slight improvement in performance. (for small lengths it doubles the performance) * Addition of a couple tests for ByteString generation * Add `Eq` and `NFData` instances for `StdGen` * Add benchmark for generation of `ShortByteString`s --- System/Random/Internal.hs | 21 ++++++++++++--------- bench/Main.hs | 11 ++++++++++- random.cabal | 2 ++ test/Spec.hs | 17 +++++++++++++++++ 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/System/Random/Internal.hs b/System/Random/Internal.hs index d965f33c2..7c0eeaff8 100644 --- a/System/Random/Internal.hs +++ b/System/Random/Internal.hs @@ -58,6 +58,7 @@ module System.Random.Internal ) where import Control.Arrow +import Control.DeepSeq (NFData) import Control.Monad.IO.Class import Control.Monad.ST import Control.Monad.ST.Unsafe @@ -70,9 +71,8 @@ import Data.ByteString.Short.Internal (ShortByteString(SBS), fromShort) import Data.Int import Data.Word import Foreign.C.Types -import Foreign.Marshal.Alloc (alloca) import Foreign.Ptr (plusPtr) -import Foreign.Storable (peekByteOff, pokeByteOff) +import Foreign.Storable (pokeByteOff) import GHC.Exts import GHC.ForeignPtr import GHC.IO (IO(..)) @@ -295,12 +295,12 @@ genShortByteStringIO n0 gen64 = do -- It is tempting to simply generate as many bytes as we still need using -- smaller generators (eg. uniformWord8), but that would result in -- inconsistent tail when total length is slightly varied. - liftIO $ - alloca $ \w64ptr -> do - runF word64LE w64 w64ptr - forM_ [0 .. nrem64 - 1] $ \i -> do - w8 :: Word8 <- peekByteOff w64ptr i - pokeByteOff ptr i w8 + liftIO $ do + let goRem64 z i = + when (i < nrem64) $ do + pokeByteOff ptr i (fromIntegral z :: Word8) + goRem64 (z `unsafeShiftR` 8) (i + 1) + goRem64 w64 0 liftIO $ IO $ \s# -> case unsafeFreezeByteArray# mba# s# of @@ -407,7 +407,10 @@ runStateGenST g action = runST $ runStateGenT g action -- | The standard pseudo-random number generator. newtype StdGen = StdGen { unStdGen :: SM.SMGen } - deriving (RandomGen, Show) + deriving (RandomGen, NFData, Show) + +instance Eq StdGen where + StdGen x1 == StdGen x2 = SM.unseedSMGen x1 == SM.unseedSMGen x2 instance RandomGen SM.SMGen where next = SM.nextInt diff --git a/bench/Main.hs b/bench/Main.hs index bb0991be9..38904c5c7 100644 --- a/bench/Main.hs +++ b/bench/Main.hs @@ -7,6 +7,7 @@ module Main (main) where +import Control.Monad import Data.Int import Data.Proxy import Data.Typeable @@ -16,11 +17,14 @@ import Gauge.Main import Numeric.Natural (Natural) import System.Random.SplitMix as SM -import System.Random +import System.Random.Monad main :: IO () main = do let !sz = 100000 + genLengths = + -- create 5000 small lengths that are needed for ShortByteString generation + runStateGen (mkStdGen 2020) $ \g -> replicateM 5000 (uniformRM (16 + 1, 16 + 7) g) defaultMain [ bgroup "baseline" [ let !smGen = SM.mkSMGen 1337 in bench "nextWord32" $ nf (genMany SM.nextWord32 smGen) sz @@ -181,6 +185,11 @@ main = do !range = (1, n - 1) in pureUniformRBench @Natural range sz ] + , bgroup "ShortByteString" + [ env (pure genLengths) $ \ ~(ns, gen) -> + bench "genShortByteString" $ + nfIO $ runStateGenT_ gen $ \g -> mapM (`uniformShortByteString` g) ns + ] ] ] ] diff --git a/random.cabal b/random.cabal index c05dff267..e1f6fec6c 100644 --- a/random.cabal +++ b/random.cabal @@ -85,6 +85,7 @@ library build-depends: base >=4.10 && <5, bytestring >=0.10 && <0.11, + deepseq >=1.1 && <2, mtl >=2.2 && <2.3, splitmix >=0.0.3 && <0.1 @@ -131,6 +132,7 @@ test-suite spec ghc-options: -Wall build-depends: base >=4.10 && <5, + bytestring >=0.10 && <0.11, random -any, smallcheck >=1.1 && <1.2, tasty >=1.0 && <1.3, diff --git a/test/Spec.hs b/test/Spec.hs index 81b469462..b14dda5a0 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -8,6 +8,8 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} module Main (main) where +import Control.Arrow (first) +import Data.ByteString.Short as SBS import Data.Coerce import Data.Int import Data.Typeable @@ -75,6 +77,7 @@ main = -- , bitmaskSpec @Word , runSpec , floatTests + , byteStringSpec ] floatTests :: TestTree @@ -103,6 +106,20 @@ showsType = showsTypeRep (typeRep (Proxy :: Proxy t)) -- , SC.testProperty "singleton" $ seeded $ Bitmask.singleton @_ @a -- ] +byteStringSpec :: TestTree +byteStringSpec = + testGroup + "ByteString" + [ SC.testProperty "genShortByteString" $ \(seed, n8) -> + let n = fromIntegral (n8 :: Word8) -- no need to generate huge collection of bytes + in SBS.length (fst (seeded (genShortByteString n) seed)) == n + , SC.testProperty "genByteString" $ \(seed, n8) -> + let n = fromIntegral (n8 :: Word8) + in SBS.toShort (fst (seeded (genByteString n) seed)) == + fst (seeded (genShortByteString n) seed) + ] + + rangeSpec :: forall a. (SC.Serial IO a, Typeable a, Ord a, UniformRange a, Show a)