Skip to content

Commit c7eb9bd

Browse files
committed
Move thawGen fromFrozenGen into new ThawedGen type class:
* Also add some laws for `FrozenGen` and `ThawedGen` This split of `FrozenGen` type class into two is needed because some mutable generators can't be cloned, becase the mutable state is stored in the monad they are running in, rather than in the mutable generator itself.
1 parent 4aab7e3 commit c7eb9bd

File tree

5 files changed

+198
-84
lines changed

5 files changed

+198
-84
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# 1.3.0
22

3+
* Move `thawGen` from `FreezeGen` into the new `ThawGen` type class. Fixes an issue with
4+
an unlawful instance of `StateGen` for `FreezeGen`.
35
* Add `modifyGen` and `overwriteGen` to the `FrozenGen` type class
46
* Add `splitGen` and `splitMutableGen`
57
* Switch `randomM` and `randomRM` to use `FrozenGen` instead of `RandomGenM`

src/System/Random/Internal.hs

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ module System.Random.Internal
3030
RandomGen(..)
3131
, StatefulGen(..)
3232
, FrozenGen(..)
33+
, ThawedGen(..)
3334
, splitGen
3435
, splitMutableGen
3536

@@ -286,40 +287,85 @@ class Monad m => StatefulGen g m where
286287
{-# INLINE uniformShortByteString #-}
287288

288289

289-
290-
-- | This class is designed for stateful pseudo-random number generators that
291-
-- can be saved as and restored from an immutable data type.
290+
-- | This class is designed for mutable pseudo-random number generators that have a frozen
291+
-- imutable counterpart that can be manipulated in pure code.
292+
--
293+
-- It also works great with frozen generators that are based on pure generators that have
294+
-- a `RandomGen` instance.
295+
--
296+
-- Here are a few of laws that are important for this interface:
297+
--
298+
-- * Roundtrip and complete destruction on overwrite:
299+
--
300+
-- @
301+
-- (overwriteGen mg fg >> freezeGen mg) = pure fg
302+
-- @
303+
--
304+
-- * Modification of mutable generator:
305+
--
306+
-- @
307+
-- overwriteGen mg fg = modifyGen mg (const ((), fg)
308+
-- @
292309
--
293-
-- It also works great on working with mutable generators that are based on a pure
294-
-- generator that has a `RandomGen` instance.
310+
-- * Freeing of mutable generator:
311+
--
312+
-- @
313+
-- freezeGen mg = modifyGen mg (\fg -> (fg, fg))
314+
-- @
295315
--
296316
-- @since 1.2.0
297317
class StatefulGen (MutableGen f m) m => FrozenGen f m where
318+
{-# MINIMAL (modifyGen|(freezeGen,overwriteGen)) #-}
298319
-- | Represents the state of the pseudo-random number generator for use with
299320
-- 'thawGen' and 'freezeGen'.
300321
--
301322
-- @since 1.2.0
302323
type MutableGen f m = (g :: Type) | g -> f
324+
303325
-- | Saves the state of the pseudo-random number generator as a frozen seed.
304326
--
305327
-- @since 1.2.0
306328
freezeGen :: MutableGen f m -> m f
307-
-- | Restores the pseudo-random number generator from its frozen seed.
308-
--
309-
-- @since 1.2.0
310-
thawGen :: f -> m (MutableGen f m)
329+
freezeGen mg = modifyGen mg (\fg -> (fg, fg))
330+
{-# INLINE freezeGen #-}
311331

312332
-- | Apply a pure function to the frozen pseudo-random number generator.
313333
--
314334
-- @since 1.3.0
315335
modifyGen :: MutableGen f m -> (f -> (a, f)) -> m a
336+
modifyGen mg f = do
337+
fg <- freezeGen mg
338+
case f fg of
339+
(a, !fg') -> a <$ overwriteGen mg fg'
340+
{-# INLINE modifyGen #-}
316341

317342
-- | Overwrite contents of the mutable pseudo-random number generator with the
318343
-- supplied frozen one
319344
--
320345
-- @since 1.3.0
321346
overwriteGen :: MutableGen f m -> f -> m ()
322347
overwriteGen mg fg = modifyGen mg (const ((), fg))
348+
{-# INLINE overwriteGen #-}
349+
350+
-- | Functionality for thawing frozen generators was split into a separate type class,
351+
-- becase not all mutable generators support functionality of creating new mutable
352+
-- generators, which is what thawing is in its essence. For this reason `StateGen` does
353+
-- not have an instance for this type class, but it has one for `FrozenGen`.
354+
--
355+
-- Here is an important law that relates this type class to `FrozenGen`
356+
--
357+
-- * Roundtrip and independence of mutable generators:
358+
--
359+
-- @
360+
-- (mapM thawGen fgs >>= mapM freezeGen) = pure fgs
361+
-- @
362+
--
363+
-- @since 1.3.0
364+
class FrozenGen f m => ThawedGen f m where
365+
-- | Create a new mutable pseudo-random number generator from its frozen state.
366+
--
367+
-- @since 1.2.0
368+
thawGen :: f -> m (MutableGen f m)
323369

324370
-- | Splits a pseudo-random number generator into two. Overwrites the mutable
325371
-- wrapper with one of the resulting generators and returns the other.
@@ -328,11 +374,11 @@ class StatefulGen (MutableGen f m) m => FrozenGen f m where
328374
splitGen :: (RandomGen f, FrozenGen f m) => MutableGen f m -> m f
329375
splitGen = flip modifyGen split
330376

331-
-- | Splits a pseudo-random number generator into two. Overwrites the mutable
332-
-- wrapper with one of the resulting generators and returns the other.
377+
-- | Splits a pseudo-random number generator into two. Overwrites the mutable wrapper with
378+
-- one of the resulting generators and returns the other as a new mutable generator.
333379
--
334380
-- @since 1.3.0
335-
splitMutableGen :: (RandomGen f, FrozenGen f m) => MutableGen f m -> m (MutableGen f m)
381+
splitMutableGen :: (RandomGen f, ThawedGen f m) => MutableGen f m -> m (MutableGen f m)
336382
splitMutableGen = splitGen >=> thawGen
337383

338384

@@ -481,7 +527,6 @@ instance (RandomGen g, MonadState g m) => StatefulGen (StateGenM g) m where
481527
instance (RandomGen g, MonadState g m) => FrozenGen (StateGen g) m where
482528
type MutableGen (StateGen g) m = StateGenM g
483529
freezeGen _ = fmap StateGen get
484-
thawGen (StateGen g) = StateGenM <$ put g
485530
modifyGen _ f = state (coerce f)
486531
{-# INLINE modifyGen #-}
487532
overwriteGen _ f = put (coerce f)

src/System/Random/Stateful.hs

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ module System.Random.Stateful
3030
-- $interfaces
3131
, StatefulGen(..)
3232
, FrozenGen(..)
33+
, ThawedGen(..)
3334
, withMutableGen
3435
, withMutableGen_
3536
, randomM
@@ -257,7 +258,7 @@ instance RandomGen r => RandomGenM (TGenM r) r STM where
257258
-- ([-74,37,-50,-2,3],IOGen {unIOGen = StdGen {unStdGen = SMGen 4273268533320920145 15251669095119325999}})
258259
--
259260
-- @since 1.2.0
260-
withMutableGen :: FrozenGen f m => f -> (MutableGen f m -> m a) -> m (a, f)
261+
withMutableGen :: ThawedGen f m => f -> (MutableGen f m -> m a) -> m (a, f)
261262
withMutableGen fg action = do
262263
g <- thawGen fg
263264
res <- action g
@@ -274,7 +275,7 @@ withMutableGen fg action = do
274275
-- 4
275276
--
276277
-- @since 1.2.0
277-
withMutableGen_ :: FrozenGen f m => f -> (MutableGen f m -> m a) -> m a
278+
withMutableGen_ :: ThawedGen f m => f -> (MutableGen f m -> m a) -> m a
278279
withMutableGen_ fg action = thawGen fg >>= action
279280

280281

@@ -311,6 +312,7 @@ uniformListM n gen = replicateM n (uniformM gen)
311312
-- @since 1.2.0
312313
randomM :: forall a g m. (Random a, RandomGen g, FrozenGen g m) => MutableGen g m -> m a
313314
randomM = flip modifyGen random
315+
{-# INLINE randomM #-}
314316

315317
-- | Generates a pseudo-random value using monadic interface and `Random` instance.
316318
--
@@ -331,6 +333,7 @@ randomM = flip modifyGen random
331333
-- @since 1.2.0
332334
randomRM :: forall a g m. (Random a, RandomGen g, FrozenGen g m) => (a, a) -> MutableGen g m -> m a
333335
randomRM r = flip modifyGen (randomR r)
336+
{-# INLINE randomRM #-}
334337

335338
-- | Wraps an 'IORef' that holds a pure pseudo-random number generator. All
336339
-- operations are performed atomically.
@@ -386,13 +389,15 @@ instance (RandomGen g, MonadIO m) => StatefulGen (AtomicGenM g) m where
386389
instance (RandomGen g, MonadIO m) => FrozenGen (AtomicGen g) m where
387390
type MutableGen (AtomicGen g) m = AtomicGenM g
388391
freezeGen = fmap AtomicGen . liftIO . readIORef . unAtomicGenM
389-
thawGen (AtomicGen g) = newAtomicGenM g
390392
modifyGen (AtomicGenM ioRef) f =
391393
liftIO $ atomicModifyIORef' ioRef $ \g ->
392394
case f (AtomicGen g) of
393395
(a, AtomicGen g') -> (g', a)
394396
{-# INLINE modifyGen #-}
395397

398+
instance (RandomGen g, MonadIO m) => ThawedGen (AtomicGen g) m where
399+
thawGen (AtomicGen g) = newAtomicGenM g
400+
396401
-- | Atomically applies a pure operation to the wrapped pseudo-random number
397402
-- generator.
398403
--
@@ -466,7 +471,6 @@ instance (RandomGen g, MonadIO m) => StatefulGen (IOGenM g) m where
466471
instance (RandomGen g, MonadIO m) => FrozenGen (IOGen g) m where
467472
type MutableGen (IOGen g) m = IOGenM g
468473
freezeGen = fmap IOGen . liftIO . readIORef . unIOGenM
469-
thawGen (IOGen g) = newIOGenM g
470474
modifyGen (IOGenM ref) f = liftIO $ do
471475
g <- readIORef ref
472476
let (a, IOGen g') = f (IOGen g)
@@ -476,6 +480,9 @@ instance (RandomGen g, MonadIO m) => FrozenGen (IOGen g) m where
476480
overwriteGen (IOGenM ref) = liftIO . writeIORef ref . unIOGen
477481
{-# INLINE overwriteGen #-}
478482

483+
instance (RandomGen g, MonadIO m) => ThawedGen (IOGen g) m where
484+
thawGen (IOGen g) = newIOGenM g
485+
479486
-- | Applies a pure operation to the wrapped pseudo-random number generator.
480487
--
481488
-- ====__Examples__
@@ -533,7 +540,6 @@ instance RandomGen g => StatefulGen (STGenM g s) (ST s) where
533540
instance RandomGen g => FrozenGen (STGen g) (ST s) where
534541
type MutableGen (STGen g) (ST s) = STGenM g s
535542
freezeGen = fmap STGen . readSTRef . unSTGenM
536-
thawGen (STGen g) = newSTGenM g
537543
modifyGen (STGenM ref) f = do
538544
g <- readSTRef ref
539545
let (a, STGen g') = f (STGen g)
@@ -543,6 +549,9 @@ instance RandomGen g => FrozenGen (STGen g) (ST s) where
543549
overwriteGen (STGenM ref) = writeSTRef ref . unSTGen
544550
{-# INLINE overwriteGen #-}
545551

552+
instance RandomGen g => ThawedGen (STGen g) (ST s) where
553+
thawGen (STGen g) = newSTGenM g
554+
546555

547556
-- | Applies a pure operation to the wrapped pseudo-random number generator.
548557
--
@@ -636,7 +645,6 @@ instance RandomGen g => StatefulGen (TGenM g) STM where
636645
instance RandomGen g => FrozenGen (TGen g) STM where
637646
type MutableGen (TGen g) STM = TGenM g
638647
freezeGen = fmap TGen . readTVar . unTGenM
639-
thawGen (TGen g) = newTGenM g
640648
modifyGen (TGenM ref) f = do
641649
g <- readTVar ref
642650
let (a, TGen g') = f (TGen g)
@@ -646,6 +654,9 @@ instance RandomGen g => FrozenGen (TGen g) STM where
646654
overwriteGen (TGenM ref) = writeTVar ref . unTGen
647655
{-# INLINE overwriteGen #-}
648656

657+
instance RandomGen g => ThawedGen (TGen g) STM where
658+
thawGen (TGen g) = newTGenM g
659+
649660

650661
-- | Applies a pure operation to the wrapped pseudo-random number generator.
651662
--
@@ -797,19 +808,17 @@ applyTGen f (TGenM tvar) = do
797808
--
798809
-- === @FrozenGen@
799810
--
800-
-- `FrozenGen` gives us ability to use any stateful pseudo-random number generator in its
801-
-- immutable form, if one exists that is. This concept is commonly known as a seed, which
802-
-- allows us to save and restore the actual mutable state of a pseudo-random number
803-
-- generator. The biggest benefit that can be drawn from a polymorphic access to a
804-
-- stateful pseudo-random number generator in a frozen form is the ability to serialize,
805-
-- deserialize and possibly even use the stateful generator in a pure setting without
806-
-- knowing the actual type of a generator ahead of time. For example we can write a
807-
-- function that accepts a frozen state of some pseudo-random number generator and
808-
-- produces a short list with random even integers.
811+
-- `FrozenGen` gives us ability to use most of stateful pseudo-random number generator in
812+
-- its immutable form, if one exists that is. The biggest benefit that can be drawn from
813+
-- a polymorphic access to a stateful pseudo-random number generator in a frozen form is
814+
-- the ability to serialize, deserialize and possibly even use the stateful generator in a
815+
-- pure setting without knowing the actual type of a generator ahead of time. For example
816+
-- we can write a function that accepts a frozen state of some pseudo-random number
817+
-- generator and produces a short list with random even integers.
809818
--
810819
-- >>> import Data.Int (Int8)
811820
-- >>> :{
812-
-- myCustomRandomList :: FrozenGen f m => f -> m [Int8]
821+
-- myCustomRandomList :: ThawedGen f m => f -> m [Int8]
813822
-- myCustomRandomList f =
814823
-- withMutableGen_ f $ \gen -> do
815824
-- len <- uniformRM (5, 10) gen

test/Spec.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ main =
9494
, uniformSpec (Proxy :: Proxy (Word8, Word16, Word32, Word64, Word))
9595
, uniformSpec (Proxy :: Proxy (Int8, Word8, Word16, Word32, Word64, Word))
9696
, uniformSpec (Proxy :: Proxy (Int8, Int16, Word8, Word16, Word32, Word64, Word))
97-
, Stateful.statefulSpec
97+
, Stateful.statefulGenSpec
9898
]
9999

100100
floatTests :: TestTree

0 commit comments

Comments
 (0)