zw324
zw324

Reputation: 27220

Haskell: Implement "randoms" (a.k.a., Ambiguous type variable)

I am reading through LYAH, and in Chapter 9, I found a curious problem. The author provides an example of implementing the "randoms" function:

randoms' :: (RandomGen g, Random a) => g -> [a]
randoms' gen = let (value, newGen) = random gen in value:randoms' newGen

Well, this compiles just fine. But if I change the second line to:

randoms' gen = (fst (random gen)) : (randoms' (snd (random gen)))

The this file reports error on loading:

IOlesson.hs:4:52:
    Ambiguous type variable `a' in the constraint:
      `Random a' arising from a use of `random' at IOlesson.hs:4:52-61
    Probable fix: add a type signature that fixes these type variable(s)
Failed, modules loaded: none.

If I change this line to:

randoms' gen = (fst (random gen)) : (randoms' gen)

Then this will do just fine, and as expected, this will return a list of all identical elements.

I am puzzled: What's so different in Miran's version and my version?

Thanks for any ideas!

Upvotes: 7

Views: 400

Answers (3)

Aaron McDaid
Aaron McDaid

Reputation: 27153

random is of type: RandomGen g => g -> (a, g)

and therefore snd (random gen) is only of type g -> g. And then it doesn't know what a is. There is a different random for each datatype you might want to generate, but in this case the compiler doesn't know whether you want a random :: g -> (Int,g) or a random :: g->(Char,g) or something else.

This explains whey (value, newGen) = random gen works. It helps the compiler to tie together it's knowledge of what a is. value must be of type a and hence it can deduce the type of random gen.

(Edited: I deleted an incorrect attempt I made at fixing it. Just stick with the original code in the question!)

Upvotes: 1

ehird
ehird

Reputation: 40797

The problem is that random takes any instance of RandGen, and returns a random value and a new generator of the same type. But the random value can be any type with an instance of Random!

random :: (Random a, RandomGen g) => g -> (a, g)

So, when you call random for the second time in the recursion, it doesn't know what the type of the first element should be! True, you don't really care about it (you throw it away with snd, after all), but the choice of a can affect the behaviour of random. So to disambiguate, you need to tell GHC what you want a to be. The easiest way is to rewrite your definition as follows:

randoms' gen = let (value, gen') = random gen in value : randoms' gen'

Because you use value as part of the resulting list, it's forced to have the same type as the a in your type signature — the element type of the resulting list. The ambiguity is resolved, and the duplicate computation of the next random number is avoided, to boot. There are ways to disambiguate this more directly (keeping the duplicate computation), but they're either ugly and confusing or involve language extensions. Thankfully, you shouldn't run into this very often, and when you do, a method like this should work to resolve the ambiguity.

Equivalently and perhaps more neatly, you can write:

randoms' gen = value : randoms' gen'
  where (value, gen') = random gen

Upvotes: 7

C. A. McCann
C. A. McCann

Reputation: 77404

Consider the type of random:

random :: (RandomGen g, Random a) => g -> (a, g)

The result tuple consists of a value of any type that's an instance of Random, and the updated RNG value. The important part is the "any instance": nothing requires both uses of random to produce a value of the same type.

In fst (random gen) there's no problem, because the value generated is whatever type the whole function needs; in snd (random gen), however, the random value is thrown away, so it's completely unknown what type it should be. Without knowing the type, Haskell can't choose the appropriate Random instance to use, hence the ambiguous type error you see.

Upvotes: 4

Related Questions