Reputation:
I'd like to understand the original meaning of functional reactive programming in Haskell and how it differs from the actual application of FRP in Javascript. Unfortunately, I have only a superficial understanding of Haskell and have to stick with Javascript.
Here is my attempt to implement Haskell's Behavior
data type in an untyped language:
// behavior :: String -> DOMHTMLElement -> a -> (a -> Event -> a) -> (a -> b)
const behavior = type => target => x => f => {
const destructiveSet = y => (x = f(x) (y), x), // A
handler = event => destructiveSet(event);
target.addEventListener(type, handler, true);
return g => g(x);
};
Line A
is necessary, because I have to mutate the initial value x
, which is held by the call stack. The function body evaluates from left to right and returns the value of the last operand of the comma operator ,
, i.e. the destructively updated version of x
.
target.addEventListener
merely subscribes the given handler to the given DOM HTML-element.
behavior
returns a function that enables read-only access to x
.
This implementation introduces a read-only abstract data type in Javascript, whose values solely exist in the call stacks of higher order functions. Provided that DOM Events are only triggered by GUI users, a program has no means to mutate values of type behavior
. It can only poll them to observe the time-varying effect.
Is this approach remotely comparable to Haskell's Behavior
?
Here is a small example - a mouse click counter (counts for four seconds):
const behavior = type => target => x => f => {
const destructiveSet = y => (x = f(x) (y), x),
handler = event => destructiveSet(event);
target.addEventListener(type, handler, true);
return g => g(x);
};
const comp = f => g => x => f(g(x));
const K = x => y => x;
const I = x => x;
const get = I;
const defer = n => f => x => setTimeout(f, n, x);
const log = prefix => x => console.log(prefix, x);
const inc = x => x + 1;
const counter = behavior("click") (document) (0) (comp(K) (inc));
console.log("click on the section above");
counter(get); // 0
defer(4000) (counter) (log("counted clicks:"));
Upvotes: 19
Views: 755
Reputation: 991
Your implementation of behavior
is closer to events in the FRP terminology, even though FRP events and DOM events have nothing in common. At its core, FRP abstracts the notion of continuous (as opposed to discrete) time. Behavior a
represents a continuous flow of values of type a
; hence its meaning is as a function from time to value.
It is literally how Conal Eliott defined it:
μ :: Behaviour a -> (Time -> a)
That notation is the one he used to describe the meaning of something as a function from that thing to a concrete computational value.
Events are "behaviours frozen in time", i.e. they represent values that occured at specific moments:
μ :: Event a -> [(Time, a)]
Because Haskell is a lazy language, streams of values can be represented as lists, which is what the above is saying.
Oddly enough, there are not many implementations of FRP that remain true to the original idea, as (I think) it is hard to come up with a performant realisation of continuous time—this C++ one seems to come close.
I encourage you to watch Conal Eliott's talks available online, his are some of the most elegant API designs I've ever seen and he explains it with crystal clarity.
Upvotes: 1