Generate a random value from a user-defined data type in Haskell
If you have a generator for the data then you can use oneof
from Test.QuickCheck.Gen
To generate a random Weapon
, whether you make Weapon
an instance of Random
or not, what you need is a way to map numbers to Weapon
s. If you derive Enum
for the type, a map to and from Int
s is defined by the compiler. So you could define
randomWeapon :: RandomGen g => g -> (Weapon, g)
randomWeapon g = case randomR (0,2) g of
(r, g') -> (toEnum r, g')
for example. With an Enum
instance, you can also easily make Weapon
an instance of Random
:
instance Random Weapon where
random g = case randomR (0,2) g of
(r, g') -> (toEnum r, g')
randomR (a,b) g = case randomR (fromEnum a, fromEnum b) g of
(r, g') -> (toEnum r, g')
If there is a possibility of adding or removing constructors from the type, the best way to keep the bounds for randomR
in sync with the type is to also derive Bounded
, as Joachim Breitner immediately suggested:
data Weapon
= Rock
| Paper
| Scissors
deriving (Bounded, Enum)
instance Random Weapon where
random g = case randomR (fromEnum (minBound :: Weapon), fromEnum (maxBound :: Weapon)) g of
(r, g') -> (toEnum r, g')
randomR (a,b) g = case randomR (fromEnum a, fromEnum b) g of
(r, g') -> (toEnum r, g')
While Daniel Fischer's Enum
approach is certainly a good general way of doing this, it's not really necessary to use an explicit mapping from Int
s. You can as well do just
instance Random Weapon where
random g = case random g of
(r,g') | r < 1/3 = (Rock , g')
| r < 2/3 = (Paper , g')
| otherwise = (Scissors, g')
using the Double
instance of Random
. This is less efficient than with a derived Enum
instance, but more flexible – for instance, you could easily define a nonequal distribution
random g = case random g of
(r,g') | r < 1/4 = (Rock , g')
| r < 1/2 = (Paper , g')
| otherwise = (Scissors, g')
where Scissors
is more likely than the other two. Of course, you should only do such a thing if the nonequal distribution is in some way canonical for your data type, certainly not in this example.
{-# LANGUAGE FlexibleInstances, UndecidableInstances,
ScopedTypeVariables, OverlappingInstances #-}
import System.Random
class (Bounded a, Enum a) => BoundedEnum a
instance (Bounded a, Enum a) => BoundedEnum a
instance BoundedEnum a => Random a where
random gen = randomR (minBound :: a, maxBound :: a) gen
randomR (f, t) gen =
(toEnum r :: a, nextGen)
where
(rnd, nextGen) = next gen
r = fromEnum f + (rnd `mod` length [f..t])
Now you can say:
r <- randomIO :: Anything
where Anything must be an instance of classes Enum and Bounded.