Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create an event stream of polymorphic functions - possible? If yes, how?

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
like image 797
apirogov Avatar asked Oct 07 '15 13:10

apirogov


1 Answers

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.

like image 89
Ørjan Johansen Avatar answered Sep 27 '22 19:09

Ørjan Johansen