views:

73

answers:

1

I want to make all types that are instances of Enum and Bounded also an instances of Random. The following code does this and should work (with the appropriate extensions enabled):

import System.Random

instance (Enum r, Bounded r) => Random r where
   randomR (hi, lo) = inFst toEnum . randomR (fromEnum hi, fromEnum lo)
      where inFst f (x,y) = (f x, y)
   random = randomR (maxBound, minBound)

But I am aware this is bad style because instance (Enum r, Bounded r) => Random r creates an instance for all r, just with type checks for Enum and Bounded rather than just putting an instance on types that are Enum and Bounded. This effectively means I'm defining an instance for all types :(.

The alternate is that I have to write standalone functions that give me the behavior I want and write some boilerplate for each type I want to be an instance of Random:

randomBoundedEnum :: (Enum r, Bounded r, RandomGen g) => g -> (r, g)
randomBoundedEnum = randomRBoundedEnum (minBound, maxBound)

randomBoundedEnumR :: (Enum r, Bounded r, RandomGen g) => (r, r) -> g -> (r, g)
randomBoundedEnumR (hi, lo) = inFst toEnum . randomR (fromEnum hi, fromEnum lo)
   where inFst f (x,y) = (f x, y)

data Side = Top | Right | Bottom | Left 
   deriving (Enum, Bounded)

-- Boilerplatey :( 
instance Random Side where
   randomR = randomBoundedEnumR
   random = randomBoundedEnum

data Hygiene = Spotless | Normal | Scruffy | Grubby | Flithy
   deriving (Enum, Bounded)

-- Boilerplatey, duplication :(
instance Random Hyigene where
   randomR = randomBoundedEnumR
   random = randomBoundedEnum

Are there any better alternatives? How should I manage this problem? Should I not even be attempt this at all? Am I overly worried about boilerplate?

+7  A: 

Yes, as I just answered to a slightly related question, you can use a newtype wrapper - this is the common and safe way to make such instances without annoying the entire community.

newtype RandomForBoundedEnum a = RfBE { unRfBE :: a}
instance (Enum a, Bounded a) => Random (RandomForBoundedEnum a) where
    ....

In this manner, users who want to use this instance simply need to wrap (or unwrap) the calls:

first unRfBE . random $ g :: (Side, StdGen)
TomMD