Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 306
zw324 Avatar asked Dec 26 '11 20:12

zw324


1 Answers

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
like image 165
ehird Avatar answered Sep 27 '22 16:09

ehird