I am currently learning FRP with reactive-banana and wanted to create a stream of random functions. I've come up with this:
-- | take number generator, and some pulse event stream, generate random function stream
mkRandom :: (Random a,RandomGen g) => g -> Event t b -> Event t ((a,a) -> a)
mkRandom rng es = (\f -> \r -> fst $ f r) <$> (accumE first $ next <$> es)
where first = flip randomR rng
next _ prev range = randomR range g
where (a,g) = prev range
It seems to work, I can use it like this:
randFuncs = mkRandom rnd (pulse 1000 time)
some = ($ (0,10::Int)) <$> randFuncs
But, of course, when I try to share that stream to generate numbers of a different type:
some2 = ($ (0,10::Double)) <$> randFuncs
The type checker complains, which I understand. Then I tried to generalize the function to the following:
mkRandom :: (RandomGen g) => g -> Event t b -> Event t (forall a. Random a => (a,a) -> a)
Then GHC complained about illegal polymorphic signature and whether I'd like to enable ImpredicativeTypes. I did it and for quite a while tried to annotate everything to make it work, but GHC always complained that it could not match the types.
My question is - is it possible to do what I want? Do I really need ImpredicativeTypes for that or am I just doing it wrong?
I thought RankNTypes should be enough for it, but I have no experience with such extensions yet.
Thanks in advance!
EDIT:
For the record, now my solution based on the helpful response is:
newtype RandomSource = Rand { getRand :: forall a. (Random a) => (a,a) -> [a] }
-- | take number generator and some pulse event stream, generate randomness stream
mkRandom :: RandomGen g => g -> Event t a -> Behavior t RandomSource
mkRandom rng es = fst <$> (accumB (next id (id,rng)) $ next <$> es)
where next _ (_,rng) = (Rand $ flip randomRs g1, g2)
where (g1,g2) = split rng
-- | take a rand. source, a range and a pulse, return stream of infinite lists of random numbers
randStream :: Random a => Behavior t RandomSource -> (a,a) -> Event t b -> Event t [a]
randStream funcs range pulse = ($ range) . getRand <$> funcs <@ pulse
ImpredicativeTypes
is an incredibly brittle extension that is not really supported or maintained and so keeps breaking further in new GHC versions.
A much better working option is to use RankNTypes
together with a newtype
wrapper:
newtype PolyRandFun = PR { getPR :: forall a. Random a => (a,a) -> a) }
This requires you to explicitly wrap and unwrap the newtype constructor, but otherwise works fine for passing around polymorphic functions like this.
Unfortunately I foresee another problem in this case. Different Random a
instances use their random generator a different amount, and in the case of e.g. Integer
the amount of primitive random numbers generated to build the Integer
result will even depend on the size of the range. So you cannot get the next g
without knowing the type and range used when actually calling your functions.
Fortunately there's a function in the System.Random
API that can get around this: split
gives you a new random generator that can be passed into subcalculations when you really need to generate several random values entirely independently.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With