4 ms·
This is where functional reactive programming really shines. It lets you model time explicitly. You can easily work with streams of events, but you can also wor
by tikhonj 13y ago
This is where functional reactive programming really shines. It lets you model time explicitly. You can easily work with streams of events, but you can also work with things that change continuously over time.
With a standard callback/state-based approach, you end up implicitly modelling time through mutable state. This has a few major disadvantages: it's difficult to talk about history and time is distinctly second-class. The "resolution" over time is also implicit and ends up depending on your implementation.
An FRP approach lets you make time explicit and a first-class citizen. In turn, this allows you to directly talk about the relationships between time-varying values and their histories--for example, you can "integrate" over time explicitly.
My favorite example comes from a little game of life applet[1] I wrote. Basically, I have an explicit timer that controls animation steps (called life)--it's a stream of () which has a value every n milliseconds. I also have a behavior called "paused" which corresponds to whether the pause button is pressed. I can combine these directly:
when (not <$> paused) (step <$ life)
(The step :: Life -> Life function represents iterating a single generation.)
I also have a stream of (x, y) coordinates from the mouse. There's a modify :: (Int, Int) -> Life -> Life function that flips a cell at the given point. I can combine these into another Life -> Life stream thanks to partial application:
modify <$> clicks
Now that I have these two streams, I can just combine them with union:
when (not <$> paused) (step <$ life) `union` (modify <$> clicks)
The cool thing about this short snippet is that it shows how I can combine three inputs--a button, a timer and the mouse--into a single stream very easily. It's also very flexible. Right now, the "when" only applies to the first input: you can modify using the mouse even when you're paused. If I wanted to change this, I could just wrap the whole thing in the when:
when (not <$> paused) (step <$ life `union` (modify <$> clicks))
Anyhow, I think FRP is an awesome approach for this sort of problem. Another thing that I did not mention is that you can write your program in a way that's "resolution-independent" over time. If your library models time continuously, you get the moral equivalent of an SVG except for reactive code! I think that's pretty cool too, although it turns out to be tricky to implement.
[1]: http://jelv.is/frp http://jelv.is/frp
- lmm 13y agoThe game of life is about as discrete-time as it gets. As you say, continuous treatment of time is fiddly, and that's what I was trying to talk about. (And you're right that FP is probably a better approach if you need history). (I suspect correct implementation using an object-oriented approach is also tricky, tbh - but you can write the naive OO version and it will work most of the time, and fail less badly, than a functional version at the same level of completeness.)
- tikhonj 13y agoThe buttons and clicks that I also support in my tiny example are not discrete. Or rather, not in the sense you meant. The grid of life is modifiable at any time--whether its running or not--and can be paused. I only talked about it a bit, but we can also model continuous time. Not things like events but things like the mouse position. After all, how you sample the mouse position should be an implementation detail! This is what I meant when I was talking about being "resolution independent". The fiddly part is not the FRP abstraction but implementing it efficiently. And, again, I'm talking about continuous time not like events (which may happen at any time but are otherwise discrete) but like a continuous function in calculus.