GniruT
GniruT

Reputation: 731

Random value of a user-defined data Type in Haskell

I've defined the following Data Type:

data NewBool = Truth | Lie deriving (Show)

and I've created a function which should return a random NewBool-value

giveMeBool :: IO()
giveMeBool = do 
     bool <- randomIO :: IO NewBool
     putStrLn("Your random boolean is"++ show bool)

I've read here that I have to make NewBool an instance of Random to use randomIO. So I've done this:

 instance Random NewBool where
 random g = case random g of
              (r,g')  | r < (1/2)= (Truth, g')
                      | otherwise= (Lie, g')

Sincerely it's just a copy&paste of what I've found in another post, but I don't understand exactly how it works? How is the value and Type of r defined? Can anyone help me? Thanks ;)

Upvotes: 1

Views: 345

Answers (1)

J. Abrahamson
J. Abrahamson

Reputation: 74374

Since random has the type (essentially)

random :: Random a => StdGen -> (a, StdGen)

if you pass it the StdGen you have, g, it'll give you a value of any type instantiating the Random type. The compiler guesses at what type you need by how you use it. In your case, it gets used in the fragment

r < (1/2)

and we can examine the types of each of these pieces

(<)   :: Ord a => a -> a -> Bool
(1/2) :: Fractional a => a

which together imply that the full type of r is

r :: (Random a, Fractional a, Ord a) => a

This isn't enough yet for Haskell to proceed, but since this is a common enough situation in numeric types (since numeric literals resolve to ambiguous types, not concrete numeric types) Haskell has a "defaulting" system. In particular, the following default configuration is in effect (by default)

default (Integer, Double)

which means that if you have an ambiguous type, constrained by classes, and either Integer or Double can satisfy those classes then, in that order, these concrete types are substituted in. In the case of r, Double instantiates Random, Ord, and Fractional so the compiler picks r :: Double.

Finally, we consider the Random instance for Double which just so happens to pick random Double values on the interval (0,1). This means that there's a 50% chance of it being greater than (1/2) and thus each of the Truth and Lie constructors will be chosen with equal odds.


Finally, for really implementing Random NewBool it's possibly better to bootstrap the instance off the very similar instance available with Bool.

instance Random NewBool where
  random g = case random g of
    (True,  g') -> (Truth, g')
    (False, g') -> (Lie,   g')

though this only works if you don't want to modify the behavior of the random generator from that of Random Bool. However, take note that a similar type resolution is occurring here—except it's far simplified since I'm matching on patterns True and False which immediately mean that the return type of random g must be (Bool, StdGen).

Upvotes: 6

Related Questions