Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write up an explanation and example of why FrozenGen is useful. #165

Merged
merged 3 commits into from
Jun 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
lehins marked this conversation as resolved.
Show resolved Hide resolved
--
-- 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]
curiousleo marked this conversation as resolved.
Show resolved Hide resolved
-- myCustomRandomList f =
lehins marked this conversation as resolved.
Show resolved Hide resolved
-- 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
lehins marked this conversation as resolved.
Show resolved Hide resolved
-- 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
lehins marked this conversation as resolved.
Show resolved Hide resolved