Fran/FRP reactive behaviors could change continuously, while reactive values change only discretely. The Reactive library keeps continuity orthogonal to reactivity. To combine continuity and reactivity, simply compose reactivity with a non-reactive type of functions of time.
One could literally use
Reactive (Time -> a), but Reactive also includes a more optimizable version as the module
Data.Fun. The denotation of
Fun t a is simply
t -> a, but it has a constructor (
K) for constant-valued functions, which occur frequently.
data Fun t a = K a | Fun (t -> a)
As with the other types in Reactive, most of the operations on
Fun values are in the form of instances of standard type classes, specifically
Arrow. As an example of optimization,
instance Functor (Fun t) where fmap f (K a) = K (f a) fmap f (Fun g) = Fun (f.g)
Data.Reactive defines reactive behaviors very simply as a type composition of
type Time = Double type ReactiveB = Reactive :. Fun Time
Wrapping the combination in a type composition (
g :. f) gives
Applicative instances for reactive behaviors that combine the corresponding instances for
Combining reactive values and non-reactive functions in this way, we can create implementations that mix data-driven (push) and demand-driven (pull) evaluation. The reactive value part is data-driven, resulting in a time function. That time function can then be polled until a new time function is pushed for further polling. As an important optimization, only poll when the time function is really time-varying (made with the
Fun constructor). If it’s constant (made with
K), then output the value just once and wait until the next push.
I’m not sure whether this elegant composition of orthogonal notions is really a good idea in practice. A benefit is the extra static typing and ability to use of the two types independently. A drawback is that the operations on reactive values have to be rewrapped and perhaps tweaked for convenient use directly on reactive behaviors.