17

Behaviors are ubiquitously defined as “time-varying value”s1.

Why? time being the dependency/parameter for varying values is very uncommon.

My intuition for FRP would be to have behaviors as event-varying values instead; it is much more common, much more simple, I wage a much more of an efficient idea, and extensible enough to support time too (tick event).

For instance, if you write a counter, you don't care about time/associated timestamps, you just care about the "Increase-button clicked" and "Decrease-button clicked" events.
If you write a game and want a position/force behavior, you just care about the WASD/arrow keys held events, etc. (unless you ban your players for moving to the left in the afternoon; how iniquitous!).

So: Why time is a consideration at all? why timestamps? why are some libraries (e.g. reactive-banana, reactive) take it up to the extent of having Future, Moment values? Why work with event-streams instead of just responding to an event occurrence? All of this just seems to over-complicate a simple idea (event-varying/event-driven value); what's the gain? what problem are we solving here? (I'd love to also get a concrete example along with a wonderful explanation, if possible).

1 Behaviors have been defined so here, here, here... & pretty much everywhere I've encountered.

MasterMastic
  • 19,099
  • 11
  • 59
  • 86
  • Time is continuous, ticks are not. "Time" does not even necessarily refer to real-time, it could be a simulation or anything that flows into only one direction. – Bergi Aug 29 '14 at 14:04
  • @Bergi Excuse me if this is a silly question, but why should we care if what drives our values is continuous or not? – MasterMastic Aug 30 '14 at 10:25
  • No, it's not silly. The nice property about continuous values is that they are not *driven*, but can be polled - they represent a value at any arbitrary "time". – Bergi Aug 30 '14 at 11:10
  • @Bergi Doesn't that mean we can't react to them (or events at all)? for instance, how can `reactimate` work? if it would just poll until it see a change in the stream then that would be very inefficient, wouldn't it? – MasterMastic Aug 30 '14 at 11:17
  • 1
    Yes, afaik you need to *sample* Behaviours to Events for handling them discretely. Whether they are actually implemented inefficiently is a different question, @ConalElliot's "push-pull FRP" paper details that. – Bergi Aug 30 '14 at 12:16

3 Answers3

12

Behaviors differ from Events primarily in that a Behavior has a value right now while an Event only has a value whenever a new event comes in.

So what do we mean by "right now"? Technically all changes are implemented as push or pull semantics over event streams, so we can only possibly mean "the most recent value as of the last event of consequence for this Behavior". But that's a fairly hairy concept—in practice "now" is much simpler.

The reasoning for why "now" is simpler comes down to the API. Here are two examples from Reactive Banana.

  1. Eventually an FRP system must always produce some kind of externally visible change. In Reactive Banana this is facilitated by the reactimate :: Event (IO ()) -> Moment () function which consumes event streams. There is no way to have a Behavior trigger external changes---you always have to do something like reactimate (someBehavior <@ sampleTickEvent) to sample the behavior at concrete times.

  2. Behaviors are Applicatives unlike Events. Why? Well, let's assume Event was an applicative and think about what happens when we have two event streams f and x and write f <*> x: since events occur all at different times the chances of f and x being defined simultaneously are (almost certainly) 0. So f <*> x would always mean the empty event stream which is useless.

    What you really want is for f <*> x to cache the most current values for each and take their combined value "all of the time". That's really confusing concept to talk about in terms of an event stream, so instead lets consider f and x as taking values for all points in time. Now f <*> x is also defined as taking values for all points in time. We've just invented Behaviors.

Mokosha
  • 2,567
  • 14
  • 30
J. Abrahamson
  • 64,404
  • 8
  • 128
  • 172
  • Note that `f x` is defined for events if time is discretized... But now you have to be aware of discrete time! – J. Abrahamson Aug 29 '14 at 14:29
  • 1
    Point 2. only requires that the value be defined at all points in time, not that it can change at all points in time. We don't need `Behavior`s that are functions of time until we want a value to change at a point in time when an event didn't occur. – Cirdec Aug 30 '14 at 13:10
  • This is generally true if your system is refined enough to differentiate the two (Reactive is, of course). My response, it should be noted, is not intended to be complete, just illustrative. – J. Abrahamson Aug 30 '14 at 13:17
  • I'm having issue understanding just one thing: How a value "now" is simpler than reading from a(n automated) state? especially when a value now is the state anyways. You're turning a basic IORef (for instance) into complex (often monadic) types, introducing primitives for each, and more. – MasterMastic Aug 30 '14 at 13:53
  • 2
    It's a choice of separating implementation from interface. The idea behind FRP is that you're usually interested in talking about streams of events whenever you use that IORef model. That said, the use of the IORef is just an implementation concern and can be inefficient. If you state your problem using FRP primitives then the library author gets to optimize how the semantics occur while your code remains high-level and "intuitive". Honestly, if you just wanted static FRP then it's very simple (the monads all vanish). – J. Abrahamson Aug 30 '14 at 15:30
  • 1
    When chosing an abstraction, a frequent consideration is to require the least powerful abstraction. If all you need is a `Functor`, your code will work with almost everything, if all you need is `Applicative`, that's not quite as general, but still most things can provide it. If you need a `Monad`, that's ok; there are a lot of things that can be Monads. If you need a specific `Monad`, like `IO` for `IORef`, then your code will only work with things built on top of `IO`. A "complex (often monadic) type" is simpler than an `IORef`. – Cirdec Aug 31 '14 at 04:21
  • Oh that's very true, although I meant it more in a way of having a state, but still, "now" really is more general. So what exactly is non-static FRP? – MasterMastic Aug 31 '14 at 15:57
  • 1
    Non-static FRP is `Behavior (Behavior a) -> Behavior a`. It doesn't combine well with accumulation and leads to major performance issues in seemingly harmless code. A lot of the monadic cruft in FRP libraries deals with making it impossible to express these ill-performant programs while providing dynamic signals and accumulation. – J. Abrahamson Aug 31 '14 at 17:02
12

Because it was the simplest way I could think of to give a precise denotation (implementation-independent meaning) to the notion of behaviors, including the sorts of operations I wanted, including differentiation and integration, as well as tracking one or more other behaviors (including but not limited to user-generated behavior).

Why? time being the dependency/parameter for varying values is very uncommon.

I suspect that you're confusing the construction (recipe) of a behavior with its meaning. For instance, a behavior might be constructed via a dependency on something like user input, possibly with additional synthetic transformation. So there's the recipe. The meaning, however, is simply a function of time, related to the time-function that is the user input. Note that by "function", I mean in the math sense of the word: a (deterministic) mapping from domain (time) to range (value), not in the sense that there's a purely programmatic description.

I've seen many questions asking why time matters and why continuous time. If you apply the simple discipline of giving a mathematical meaning in the style of denotational semantics (a simple and familiar style for functional programmers), the issues become much clearer.

If you really want to grok the essence of and thinking behind FRP, I recommend you read my answer to "Specification for a Functional Reactive Programming language" and follow pointers, including "What is Functional Reactive Programming".

Community
  • 1
  • 1
Conal
  • 18,206
  • 2
  • 34
  • 40
4

Conal Elliott's Push-Pull FRP paper describes event-varying data, where the only points in time that are interesting are when events occcur. Reactive event-varying data is the current value and the next Event that will change it. An Event is a Future point in the event-varying Reactive data.

data Reactive a = a ‘Stepper ‘ Event a
newtype Event a = Ev (Future (Reactive a))

The Future doesn't need to have a time associated with it, it just need to represent the idea of a value that hasn't happened yet. In an impure language with events, for example, a future can be an event handle and a value. When the event occurs, you set the value and raise the handle.

Reactive a has a value for a at all points in time, so why would we need Behaviors? Let's make a simple game. In between when the user presses the WASD keys, the character, accelerated by the force applied, still moves on the screen. The character's position at different points in time is different, even though no event has occurred in the intervening time. This is what a Behavior describes - something that not only has a value at all points in time, but its value can be different at all points in time, even with no intervening events.

One way to describe Behaviors would be to repeat what we just stated. Behaviors are things that can change in-between events. In-between events they are time-varying values, or functions of time.

type Behavior a = Reactive (Time -> a)

We don't need Behavior, we could simply add events for clock ticks, and write all of the logic in our entire game in terms of these tick events. This is undesirable to some developers as the code declaring what our game is is now intermingled with the code providing how it is implemented. Behaviors allow the developer to separate this logic between the description of the game in terms of time-varying variables and the implementation of the engine that executes that description.

Conal
  • 18,206
  • 2
  • 34
  • 40
Cirdec
  • 23,492
  • 2
  • 45
  • 94