Skip to content

Commit

Permalink
Write up an explanation and example of why FrozenGen is useful. (#165)
Browse files Browse the repository at this point in the history
* Write up an explanation and example of why FrozenGen is useful. Fix #146

* Optimize `mod` -> `rem`

* Add some tests for new functions: `uniformDoublePositive01M` and `uniformFloatPositive01M`
  • Loading branch information
lehins authored Jun 16, 2020
1 parent 81edfff commit 56363bb
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 9 deletions.
3 changes: 2 additions & 1 deletion random.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/System/Random/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 41 additions & 2 deletions src/System/Random/Stateful.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions test/Spec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ main =
, runSpec
, floatTests
, byteStringSpec
, SC.testProperty "uniformRangeWithinExcludedF" $ seeded Range.uniformRangeWithinExcludedF
, SC.testProperty "uniformRangeWithinExcludedD" $ seeded Range.uniformRangeWithinExcludedD
]

floatTests :: TestTree
Expand Down
16 changes: 11 additions & 5 deletions test/Spec/Range.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

0 comments on commit 56363bb

Please sign in to comment.