diff --git a/random.cabal b/random.cabal index d7954b333..8d6e25e6f 100644 --- a/random.cabal +++ b/random.cabal @@ -142,7 +142,8 @@ test-suite doctests mwc-random >=0.13 && <0.15, primitive >=0.6 && <0.8, random -any, - unliftio >=0.2 && <0.3 + unliftio >=0.2 && <0.3, + vector >= 0.10 && <0.14 test-suite spec type: exitcode-stdio-1.0 diff --git a/src/System/Random/Internal.hs b/src/System/Random/Internal.hs index 61719988c..554e99fbe 100644 --- a/src/System/Random/Internal.hs +++ b/src/System/Random/Internal.hs @@ -897,7 +897,7 @@ boundedExclusiveIntegralM s gen = go twoToK = (1 :: a) `shiftL` k modTwoToKMask = twoToK - 1 - t = (twoToK - s) `mod` s + t = (twoToK - s) `rem` s -- `rem`, instead of `mod` because `twoToK >= s` is guaranteed go :: (Bits a, Integral a, StatefulGen g m) => m a go = do x <- uniformIntegralWords n gen diff --git a/src/System/Random/Stateful.hs b/src/System/Random/Stateful.hs index 842c1e081..47bd3e30c 100644 --- a/src/System/Random/Stateful.hs +++ b/src/System/Random/Stateful.hs @@ -624,8 +624,7 @@ runSTGen_ g action = fst $ runSTGen g action -- $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. +-- and restore its internal state in addition to generating pseudo-random numbers. -- -- Here is an example instance for the monadic pseudo-random number generator -- from the @mwc-random@ package: @@ -642,6 +641,46 @@ runSTGen_ g action = fst $ runSTGen g action -- > thawGen = MWC.restore -- > freezeGen = MWC.save -- +-- === @FrozenGen@ +-- +-- `FrozenGen` gives us ability to use any stateful pseudo-random number generator in its +-- immutable form, if one exists that is. This concept is commonly known as a seed, which +-- allows us to save and restore the actual mutable state of a pseudo-random number +-- generator. The biggest benefit that can be drawn from a polymorphic access to a +-- stateful pseudo-random number generator in a frozen form is the ability to serialize, +-- deserialize and possibly even use the stateful generator in a pure setting without +-- knowing the actual type of a generator ahead of time. For example we can write a +-- function that accepts a frozen state of some pseudo-random number generator and +-- produces a short list with random even integers. +-- +-- >>> import Data.Int (Int8) +-- >>> :{ +-- myCustomRandomList :: FrozenGen f m => f -> m [Int8] +-- myCustomRandomList f = +-- withMutableGen_ f $ \gen -> do +-- len <- uniformRM (5, 10) gen +-- replicateM len $ do +-- x <- uniformM gen +-- pure $ if even x then x else x + 1 +-- :} +-- +-- and later we can apply it to a frozen version of a stateful generator, such as `STGen`: +-- +-- >>> print $ runST $ myCustomRandomList (STGen (mkStdGen 217)) +-- [-50,-2,4,-8,-58,-40,24,-32,-110,24] +-- +-- or a @Seed@ from @mwc-random@: +-- +-- >>> import Data.Vector.Primitive as P +-- >>> print $ runST $ myCustomRandomList (MWC.toSeed (P.fromList [1,2,3])) +-- [24,40,10,40,-8,48,-78,70,-12] +-- +-- Alternatively, instead of discarding the final state of the generator, as it happens +-- above, we could have used `withMutableGen`, which together with the result would give +-- us back its frozen form. This would allow us to store the end state of our generator +-- somewhere for the later reuse. +-- +-- -- $references -- -- 1. Guy L. Steele, Jr., Doug Lea, and Christine H. Flood. 2014. Fast diff --git a/test/Spec.hs b/test/Spec.hs index 9cc8b5276..63eb126f3 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -69,6 +69,8 @@ main = , runSpec , floatTests , byteStringSpec + , SC.testProperty "uniformRangeWithinExcludedF" $ seeded Range.uniformRangeWithinExcludedF + , SC.testProperty "uniformRangeWithinExcludedD" $ seeded Range.uniformRangeWithinExcludedD ] floatTests :: TestTree diff --git a/test/Spec/Range.hs b/test/Spec/Range.hs index a235f56c1..9a61b59a7 100644 --- a/test/Spec/Range.hs +++ b/test/Spec/Range.hs @@ -3,9 +3,11 @@ module Spec.Range , bounded , singleton , uniformRangeWithin - , uniformRangeWithinExcluded + , uniformRangeWithinExcludedF + , uniformRangeWithinExcludedD ) where +import System.Random.Internal import System.Random.Stateful import Data.Proxy @@ -29,8 +31,12 @@ uniformRangeWithin _ gen (l, r) = runStateGen_ gen $ \g -> (\result -> min l r <= result && result <= max l r) <$> uniformRM (l, r) g -uniformRangeWithinExcluded :: - (RandomGen g, UniformRange a, Ord a) => Proxy a -> g -> (a, a) -> Bool -uniformRangeWithinExcluded _ gen (l, r) = +uniformRangeWithinExcludedF :: RandomGen g => g -> Bool +uniformRangeWithinExcludedF gen = runStateGen_ gen $ \g -> - (\result -> min l r <= result && (l == r || result < max l r)) <$> uniformRM (l, r) g + (\result -> 0 < result && result <= 1) <$> uniformFloatPositive01M g + +uniformRangeWithinExcludedD :: RandomGen g => g -> Bool +uniformRangeWithinExcludedD gen = + runStateGen_ gen $ \g -> + (\result -> 0 < result && result <= 1) <$> uniformDoublePositive01M g