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

1

6

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
Brumaire answered 7/10, 2015 at 13:55 Comment(2)
I've been told reactive-banana 1.0 (current master) will simplify the types in non-trivial and substantial ways, such as not requiring RankNTypes (in some scenarios?), so you might give it a try.Architectural
@ErikAllik The API changes for reactive-banana 1.0 mainly concern the type parameter t. The question at hand is unrelated to this.Aton
M
7

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.

Mitchelmitchell answered 7/10, 2015 at 14:27 Comment(4)
Alas, I can confirm that ImpredicativeTypes is very brittle – but can you say why it's so little-maintained? I feel it should be a rather natural extension to RankN.Jactitation
@Jactitation Explanation by Simon PJJonahjonas
@leftaroundabout, I personally cannot, but my impression is that it's a "deep" problem. GHC's type checker is apparently fundamentally incapable of doing it right, and no one knows how to write a type checker that does it right, offers all the other pretty things, and gives vaguely human-readable error messages.Eclogite
Thanks! The necessary unwrapping is a bit unsatisfactory, but it works!Brumaire

© 2022 - 2024 — McMap. All rights reserved.