As @AJFarmar noted, you can use the elements
combinator to pick a random element from a list, instead of taking the head of a shuffled version:
genAnimal :: Gen String
genAnimal = elements ["tiger","rabbit","dragon","snake","rat","ox",
"pig","sheep","horse","monkey","dog"]
Also, an applicative style can generally be more succinct, particularly if you introduce helper functions for the pure computations that combine the results:
genPrize :: Gen Int
genPrize = choose (10,1000)
genTicket :: Gen String
genTicket = ticket <$> replicateM 3 genAnimal <*> genPrize
where ticket animals prize = unwords animals ++ " " ++ show prize
A few other miscellaneous improvements might come from:
- shifting your perspective on your generators to generate words that you combine at the end, instead of strings that have to be
unword
ed and glued together with blanks;
- or better yet, introducing some datatypes to better model your problem and providing a utility function to print it in the appropriate
String
form only when needed;
- getting rid of the
gen
prefixes that clutter everything up
- using the
vectorOf
alias for replicateM
which is a little more readable in this context.
This might lead to something like:
{-# OPTIONS_GHC -Wall #-}
module Lottery where
import Test.QuickCheck
type Animal = String
data Ticket = Ticket [Animal] Int
pticket :: Ticket -> String
pticket (Ticket ts prz) = unwords ts ++ ' ':show prz
tickets :: Gen [Ticket]
tickets = vectorOf 6 (oneof [winner, loser])
where winner = Ticket <$> (replicate 3 <$> animal) <*> prize
loser = Ticket <$> (vectorOf 3 animal) <*> prize
animal = elements ["tiger","rabbit","dragon","snake","rat","ox",
"pig","sheep","horse","monkey","dog"]
prize = choose (10,1000)
main :: IO ()
main = print . map pticket =<< generate tickets
which I think looks pretty good.