Reputation: 27220
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
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
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
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