Skip to content

Commit 1e1e1b4

Browse files
Speed up generation by threading seeds instead of splitting them in non-Gen part of GenT (#57)
1 parent 2cda6b9 commit 1e1e1b4

File tree

2 files changed

+95
-41
lines changed

2 files changed

+95
-41
lines changed

src/Constrained/GenT.hs

Lines changed: 95 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ module Constrained.GenT (
2424
suchThatT,
2525
suchThatWithTryT,
2626
scaleT,
27+
resizeT,
2728
firstGenT,
2829
tryGenT,
2930
chooseT,
@@ -34,7 +35,6 @@ module Constrained.GenT (
3435
vectorOfT,
3536
listOfUntilLenT,
3637
listOfT,
37-
resizeT,
3838
strictGen,
3939
looseGen,
4040

@@ -69,6 +69,9 @@ import GHC.Stack
6969
import System.Random
7070
import Test.QuickCheck hiding (Args, Fun)
7171
import Test.QuickCheck.Gen
72+
import Test.QuickCheck.Random
73+
import Control.Arrow (second)
74+
import Control.Monad.Trans
7275

7376
-- ==============================================================
7477
-- The GE Monad
@@ -90,6 +93,62 @@ instance Monad GE where
9093
GenError es >>= _ = GenError es
9194
Result a >>= k = k a
9295

96+
------------------------------------------------------------------------
97+
-- Threading gen monad
98+
------------------------------------------------------------------------
99+
100+
-- The normal Gen monad always splits the seed when doing >>=. This is for very
101+
-- good reasons - it lets you write generators that generate infinite data to
102+
-- the left of a >>= and let's your generators be very lazy!
103+
104+
-- A traditional GenT m a implementation would inherit this splitting behaviour
105+
-- in order to let you keep writing infinite and lazy things to the left of >>=
106+
-- on the GenT m level. Now, the thing to realize about this is that unless
107+
-- your code is very carefully written to avoid it this means you're going to
108+
-- end up with unnecessary >>=s and thus unnecessary splits.
109+
110+
-- To get around this issue of unnecessary splits we introduce a threading GenT
111+
-- implementation here that sacrifices letting you do infinite (and to some
112+
-- extent lazy) structures to the left of >>= on the GenT m level, but doesn't
113+
-- prohibit you from doing so on the Gen level.
114+
115+
-- This drastically reduces the number of seed splits while still letting you
116+
-- write lazy and infinite generators in Gen land by being a little bit more
117+
-- careful. It works great for constrained-generators in particular, which has
118+
-- a tendency to be strict and by design avoids inifinte values.
119+
120+
liftGenToThreading :: Monad m => Gen a -> ThreadingGenT m a
121+
liftGenToThreading g = ThreadingGen $ \seed size -> do
122+
let (seed', seed'') = split seed
123+
pure (seed'', unGen g seed' size)
124+
125+
runThreadingGen :: Functor m => ThreadingGenT m a -> Gen (m a)
126+
runThreadingGen g = MkGen $ \seed size -> do
127+
snd <$> unThreadingGen g seed size
128+
129+
strictGetSize :: Applicative m => ThreadingGenT m Int
130+
strictGetSize = ThreadingGen $ \ seed size -> pure (seed, size)
131+
132+
scaleThreading :: (Int -> Int) -> ThreadingGenT m a -> ThreadingGenT m a
133+
scaleThreading f sg = ThreadingGen $ \ seed size -> unThreadingGen sg seed (f size)
134+
135+
newtype ThreadingGenT m a = ThreadingGen { unThreadingGen :: QCGen -> Int -> m (QCGen, a) }
136+
137+
instance Functor m => Functor (ThreadingGenT m) where
138+
fmap f (ThreadingGen g) = ThreadingGen $ \ seed size -> second f <$> g seed size
139+
140+
instance Monad m => Applicative (ThreadingGenT m) where
141+
pure a = ThreadingGen $ \ seed _ -> pure (seed, a)
142+
(<*>) = ap
143+
144+
instance Monad m => Monad (ThreadingGenT m) where
145+
ThreadingGen g >>= k = ThreadingGen $ \ seed size -> do
146+
(seed', a) <- g seed size
147+
unThreadingGen (k a) seed' size
148+
149+
instance MonadTrans ThreadingGenT where
150+
lift m = ThreadingGen $ \ seed _ -> (seed,) <$> m
151+
93152
------------------------------------------------------------------------
94153
-- The GenT monad
95154
-- An environment monad on top of GE
@@ -108,19 +167,17 @@ data GenMode
108167

109168
-- | A `Gen` monad wrapper that allows different generation modes and different
110169
-- failure types.
111-
newtype GenT m a = GenT {runGenT :: GenMode -> [NonEmpty String] -> Gen (m a)}
170+
newtype GenT m a = GenT {runGenT :: GenMode -> [NonEmpty String] -> ThreadingGenT m a}
112171
deriving (Functor)
113172

114173
instance Monad m => Applicative (GenT m) where
115-
pure a = GenT (\_ _ -> pure @Gen (pure @m a))
174+
pure a = GenT (\_ _ -> pure a)
116175
(<*>) = ap
117176

118-
-- I think this might be an inlined use of the Gen monad transformer?
119177
instance Monad m => Monad (GenT m) where
120-
GenT m >>= k = GenT $ \mode -> \msgs -> MkGen $ \r n -> do
121-
let (r1, r2) = split r
122-
a <- unGen (m mode msgs) r1 n
123-
unGen (runGenT (k a) mode msgs) r2 n
178+
GenT m >>= k = GenT $ \mode msgs -> do
179+
a <- m mode msgs
180+
runGenT (k a) mode msgs
124181

125182
instance MonadGenError m => MonadFail (GenT m) where
126183
fail s = genError s
@@ -168,15 +225,15 @@ instance MonadGenError GE where
168225

169226
-- | calls to genError and fatalError, add the stacked messages in the monad.
170227
instance MonadGenError m => MonadGenError (GenT m) where
171-
genErrorNE e = GenT $ \_ xs -> pure $ genErrors (add e xs)
172-
genErrors es = GenT $ \_ xs -> pure $ genErrors (cat es xs)
228+
genErrorNE e = GenT $ \_ xs -> lift $ genErrors (add e xs)
229+
genErrors es = GenT $ \_ xs -> lift $ genErrors (cat es xs)
173230

174231
-- Perhaps we want to turn genError into fatalError, if mode_ is Strict?
175-
fatalErrorNE e = GenT $ \_ xs -> pure $ fatalErrors (add e xs)
176-
fatalErrors es = GenT $ \_ xs -> pure $ fatalErrors (cat es xs)
232+
fatalErrorNE e = GenT $ \_ xs -> lift $ fatalErrors (add e xs)
233+
fatalErrors es = GenT $ \_ xs -> lift $ fatalErrors (cat es xs)
177234

178235
-- Perhaps we want to turn fatalError into genError, if mode_ is Loose?
179-
explainNE e (GenT f) = GenT $ \mode es -> fmap (explainNE e) (f mode es)
236+
explainNE e (GenT f) = GenT $ \mode es -> ThreadingGen $ \ seed size -> explainNE e $ unThreadingGen (f mode es) seed size
180237

181238
-- ====================================================
182239
-- useful operations on NonEmpty
@@ -270,29 +327,25 @@ listFromGE = fromGE (const []) . explain "listFromGE"
270327
-- Useful operations on GenT
271328

272329
-- | Run a t`GenT` generator in `Strict` mode
273-
strictGen :: GenT m a -> Gen (m a)
274-
strictGen genT = runGenT genT Strict []
330+
strictGen :: Functor m => GenT m a -> Gen (m a)
331+
strictGen genT = runThreadingGen $ runGenT genT Strict []
275332

276333
-- | Run a t`GenT` generator in `Loose` mode
277-
looseGen :: GenT m a -> Gen (m a)
278-
looseGen genT = runGenT genT Loose []
334+
looseGen :: Functor m => GenT m a -> Gen (m a)
335+
looseGen genT = runThreadingGen $ runGenT genT Loose []
279336

280337
-- | Turn a t`GenT` generator into a `Gen` generator in `Strict` mode
281338
genFromGenT :: GenT GE a -> Gen a
282339
genFromGenT genT = errorGE <$> strictGen genT
283340

284-
-- | Locally change the generation size
285-
resizeT :: (Int -> Int) -> GenT m a -> GenT m a
286-
resizeT f (GenT gm) = GenT $ \mode msgs -> sized $ \sz -> resize (f sz) (gm mode msgs)
287-
288341
-- | Turn a `Gen` generator into a t`GenT` generator that never fails.
289-
pureGen :: Applicative m => Gen a -> GenT m a
290-
pureGen gen = GenT $ \_ _ -> pure <$> gen
342+
pureGen :: Monad m => Gen a -> GenT m a
343+
pureGen gen = GenT $ \_ _ -> liftGenToThreading gen
291344

292345
-- | Lift `listOf` to t`GenT`
293346
listOfT :: MonadGenError m => GenT GE a -> GenT m [a]
294347
listOfT gen = do
295-
lst <- pureGen . listOf $ runGenT gen Loose []
348+
lst <- pureGen . listOf $ runThreadingGen $ runGenT gen Loose []
296349
catGEs lst
297350

298351
-- | Generate a list of elements of length at most @goalLen@, but accepting
@@ -310,18 +363,18 @@ listOfUntilLenT gen goalLen validLen =
310363
genList `suchThatT` validLen . length
311364
where
312365
genList = do
313-
res <- pureGen . vectorOf goalLen $ runGenT gen Loose []
366+
res <- pureGen . vectorOf goalLen $ runThreadingGen $ runGenT gen Loose []
314367
catGEs res
315368

316369
-- | Lift `vectorOf` to t`GenT`
317370
vectorOfT :: MonadGenError m => Int -> GenT GE a -> GenT m [a]
318371
vectorOfT i gen = GenT $ \mode _ -> do
319-
res <- fmap sequence . vectorOf i $ runGenT gen Strict []
372+
res <- liftGenToThreading $ fmap sequence . vectorOf i $ runThreadingGen $ runGenT gen Strict []
320373
case mode of
321-
Strict -> pure $ runGE res
374+
Strict -> lift $ runGE res
322375
Loose -> case res of
323-
FatalError es -> pure $ genErrors es
324-
_ -> pure $ runGE res
376+
FatalError es -> lift $ genErrors es
377+
_ -> lift $ runGE res
325378

326379
infixl 2 `suchThatT`
327380

@@ -351,16 +404,20 @@ suchThatWithTryT tries g p = do
351404

352405
-- | Lift `scale` to t`GenT`
353406
scaleT :: (Int -> Int) -> GenT m a -> GenT m a
354-
scaleT sc (GenT gen) = GenT $ \mode msgs -> scale sc $ gen mode msgs
407+
scaleT sc (GenT gen) = GenT $ \mode msgs -> scaleThreading sc $ gen mode msgs
408+
409+
-- | Lift `resize` to t`GenT`
410+
resizeT :: Int -> GenT m a -> GenT m a
411+
resizeT = scaleT . const
355412

356413
-- | Access the `GenMode` we are running in, useful to decide e.g. if we want
357414
-- to re-try in case of a `GenError` or give up
358-
getMode :: Applicative m => GenT m GenMode
359-
getMode = GenT $ \mode _ -> pure (pure mode)
415+
getMode :: Monad m => GenT m GenMode
416+
getMode = GenT $ \mode _ -> pure mode
360417

361418
-- | Get the current stack of `explain` above you
362-
getMessages :: Applicative m => GenT m [NonEmpty String]
363-
getMessages = GenT $ \_ msgs -> pure (pure msgs)
419+
getMessages :: Monad m => GenT m [NonEmpty String]
420+
getMessages = GenT $ \_ msgs -> pure msgs
364421

365422
-- | Locally change the generation mode
366423
withMode :: GenMode -> GenT m a -> GenT m a
@@ -377,7 +434,7 @@ frequencyT gs = do
377434
msgs <- getMessages
378435
r <-
379436
explain "suchThatT in oneofT" $
380-
pureGen (frequency [(f, runGenT g mode msgs) | (f, g) <- gs]) `suchThatT` isOk
437+
pureGen (frequency [(f, runThreadingGen $ runGenT g mode msgs) | (f, g) <- gs]) `suchThatT` isOk
381438
runGE r
382439

383440
-- | Lift `choose` to t`GenT`, failing with a `genError` in case of an empty interval
@@ -388,18 +445,16 @@ chooseT (a, b)
388445

389446
-- | Get the size provided to the generator
390447
sizeT :: Monad m => GenT m Int
391-
sizeT = GenT $ \mode msgs -> sized $ \n -> runGenT (pure n) mode msgs
448+
sizeT = GenT $ \_ _ -> strictGetSize
392449

393450
-- ==================================================================
394451
-- Reflective analysis of the internal GE structure of (GenT GE x)
395452
-- This allows "catching" internal FatalError and GenError, and allowing
396453
-- the program to control what happens in those cases.
397454

398455
-- | Always succeeds, but returns the internal GE structure for analysis
399-
inspect :: forall m x. MonadGenError m => GenT GE x -> GenT m (GE x)
400-
inspect (GenT f) = GenT g
401-
where
402-
g mode msgs = do geThing <- f mode msgs; pure @Gen (pure @m geThing)
456+
inspect :: forall m a. MonadGenError m => GenT GE a -> GenT m (GE a)
457+
inspect (GenT f) = GenT $ \ mode msgs -> liftGenToThreading $ runThreadingGen $ f mode msgs
403458

404459
-- | Ignore all kinds of Errors, by squashing them into Nothing
405460
tryGenT :: MonadGenError m => GenT GE a -> GenT m (Maybe a)

src/Constrained/NumOrd.hs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ import Data.List (nub)
7171
import Data.List.NonEmpty (NonEmpty ((:|)))
7272
import qualified Data.List.NonEmpty as NE
7373
import Data.Maybe
74-
import qualified Data.Set as Set
7574
import Data.Typeable (typeOf)
7675
import Data.Word
7776
import GHC.Int

0 commit comments

Comments
 (0)