[Haskell-cafe] Using MonadRandom with QuickCheck

22 views
Skip to first unread message

Magnus Grindal Bakken

unread,
Aug 21, 2016, 3:38:08 PM8/21/16
to haskel...@haskell.org
Is there some easy way to use the Gen type from QuickCheck with the
MonadRandom class?

Links to the packages in question:
https://hackage.haskell.org/package/MonadRandom
https://hackage.haskell.org/package/QuickCheck

Some background in case there's an easier way to solve the problem:
I'm toying around with an API for a simple turn-based board game. The
main type in my API is called GameState, and I'm trying to define an
Arbitrary instance for it so I can set up invariants with QuickCheck.
The Arbitrary instance should produce game states that are the result
of playing a random number of random moves.

At certain points in the gameplay the game itself needs to invoke some
randomness. I've designed the public game API to have basically just
two functions:

playAction :: ActionData -> GameState -> GameState
evaluateGameState :: MonadRandom m => GameState -> m GameState

The usage is supposed to be that the client first calls playAction
with an ActionData value that depends on the current move, and
afterwards calls evaluateGameState to evaluate whatever random events
need to happen at that point (e.g. reshuffling a deck of cards). That
way my main "action" logic doesn't need to concern itself with any
form of randomness.

I'm using MonadRandom from Control.Monad.Random because it seems like
a much nicer way to generate random numbers than using RandomGen
directly. However I can't figure out how to make it play nice with
QuickCheck. I can easily set up my Arbitrary instance to call
playAction an arbitrary number of times, but I can't call
evaluateGameState since it lives in the other monad. It would work if
Gen were an instance of MonadRandom, but I can't quite work out how to
write that instance since most functions on Gen only work if the
output type is an instance of Arbitrary, while MonadRandom needs to
work with any types that are part of the Random class. This part
works:

instance MonadRandom Gen where
getRandomR = choose

But I don't know how to define the other functions on MonadRandom for Gen.

I think I might also be able to use the PropertyM monad transformer:
https://hackage.haskell.org/package/QuickCheck-2.9.1/docs/Test-QuickCheck-Monadic.html,
but monad transformers always confuse me so I haven't been able to
figure out how to do that yet either.

I've thought about rewriting the API to use the Gen type instead of
MonadRandom, but it feels kind of iffy to have the API depend on a
testing library.

Has anyone tried to use these libraries together before?
_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.

Li-yao Xia

unread,
Aug 21, 2016, 4:37:35 PM8/21/16
to Magnus Grindal Bakken, haskel...@haskell.org
Hi Magnus,

Import Test.QuickCheck.Gen to get the internals of Gen, which AFAIK is
currently the only way to get to the underlying random generator (QCGen).

getRandom = MkGen $ \g _ -> fst (random g)
-- repeat with randomRs, randoms.

I don't know why Gen is not an instance of MonadRandom either. It seems
reasonable to add one, though it might go against some design principle
of QuickCheck by ignoring the size parameter.

Regards,
Li-yao

Magnus Grindal Bakken

unread,
Aug 27, 2016, 6:59:39 AM8/27/16
to Haskell-cafe, haskel...@haskell.org, li-ya...@ens.fr
Thanks, Li-yao! For anyone else reading this, this is the MonadRandom instance
I ended up with:

mkGen :: (QCGen -> a) -> Gen a
mkGen f = MkGen $ \g _ -> f g

instance MonadRandom Gen where
    getRandom = mkGen (fst . random)
    getRandoms = mkGen randoms
    getRandomR range = mkGen (fst . randomR range)
    getRandomRs range = mkGen (randomRs range)

With that said, I found that in many cases to get the game states I needed for
my property tests I had to use more specific generators that took input such as
a range of number of moves, whether the game was finished or not, the current
action, etc., so it didn't end up being as useful for me as I hoped.
Reply all
Reply to author
Forward
0 new messages