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 Weapons. If you derive Enum for the type, a map to and from Ints 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 Ints. 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.