Version française
Home     About     Download     Resources     Contact us    
Browse thread
more on frp (was Re: [Caml-list] Re: Recursion on React.events)
[ Home ] [ Index: by date | by threads ]
[ Search: ]

[ Message by date: previous | next ] [ Message in thread: previous | next ] [ Thread: previous | next ]
Date: -- (:)
From: Daniel_Bünzli <daniel.buenzli@e...>
Subject: more on frp (was Re: [Caml-list] Re: Recursion on React.events)
> Personally I've yet to read any comprehensible introduction to FRP.

I agree my documentation can be dry for a newcomer to frp. It's not a
tutorial about frp. Its aim is to answer any questions a proficient
user of the library could have and I hope that it fulfilths at least
that goal.

But frp itself is not that hard to comprehend. Unfortunately the only
things I can point you to is (haskell & scheme) academic papers which
also tend to quickly talk about (nonetheless interesting) semantic and
implementation issues.

Maybe the following along with react's documentation can help you to
get started. It's far from perfect, it was quickly written, just a try.

* Events and signals

Frp manipulates two kinds of values. Events and signals (the latter
are a.k.a. behaviours in many implementations). Events and signals are
values that depend on time.

An event has values (occurences) at specific points in time. For
example it could represent the keysym of the key depressed by a
user. Events can be understood as follows :

 type 'a event = time -> 'a option

Applying the function with the current time tells you whether there's
an event occurence or not at that time. In our example whether a key
was depressed or not.

A signal has a value at any point in time. It represents values that
vary continuously like the current value of a slider. The type for
signals can be seen as

 type 'a signal = time -> 'a

Applying the function with the current time tells you the current
value of the signal. In our example it tells you the current value of
the slider. In general, state, i.e. anything that you'd fit in a
reference cell or a mutable field is a candidate to become a signal.

As you are an astute reader you will already have remarked that the
following approach could have been taken :

 type 'a event = 'a option signal

In fact events and signals are duals. One can be implemented in terms
of the other (signals can be seen as the event occurences of its value changes),
However it makes sense to keep the two concepts separate (code
clarity, efficiency issues, semantic details with comparison).

Now given events or signals, frp provides you combinators to define
new derived events. These combinators are in the modules React.E and
React.S.

For example suppose we are given the following event (for now don't
bother about how it could be defined) :

 let keydown : keysym event = ...

which represents keysyms of the keys depressed by a user. We would
like to define an event that corresponds to the character of the
keysym. We have the following translation function :

 let char_of_keysym : keysym -> char = fun ks -> ...

So we can define our new event as :

 let keychar = E.map char_of_keysym keydown

Every time keydown gets an occurence, keychar gets one. Except the
value of the occurence for keychar is the value of keydown transformed
by char_of_keysym.

Suppose we want to remember the last keysym that was issued. That's a
signal, it is defined at any point in time. For that the S.hold
combinator can be used, it remembers the last occurence of an event :

 let last_keysym = S.hold null_keysym keydown

Each time keydown gets an occurence S.hold updates the signal with the
new occurence value. null_keysym is used to initalize the signal (it
needs a value at the time it is created and the keydown event may not
give us one at that time).

Now we would like to concatenate the stream of keychar occurences into
a string. That is we want a string signal that holds at any point in
time the key characters that were depressed so far. It can be defined
by folding over the sequence of keychar occurences :

 let istr = S.fold (fun acc c = acc ^ (String.make 1 c)) "" keychar

So istr is a string signal. Its initial value is "". Each time keychar
gets an occurence, the signal is updated by applying the folding
function to the current value of the signal with the event
occurence. The result of the folding function is the new value for the
signal.

Suppose we have two float signals a and b. We want to have a signal
that keeps the sum of a and b at any time. For that we lift the (+.)
function to act from float values to float signals as follows :

  let sum = S.l2 (+.) a b

Now whenever a or b changes sum is automatically updated with the sum
of the current value of a or b.

To sum up event and signal combinators take signals and events
arguments and return a new signal or event r. r is said to depend on
the arguments given to the combinator. So effectively what you are
building with combinators is a graph of data dependencies. Think about
it like the dependencies in a spreadsheet (e.g. the sum example
above). What frp gives you is that whenever a value changes in the
graph all the transitive dependencies get automatically updated in a
consistant way (glitches are avoided by updating all dependee before
updating the dependent).

Now the question is how can I update signal values and generate event
occurences, combinators only take other events and signals, they don't
allow to update values or generate occurences ? The answer is primitive
events and signals.

Before moving on. I should stress that the notion of time in react is
purely relative (this happens before that). There's no concrete value of
type time as suggested above, this notion of time is only used as a tool
to define the denotational semantics of events and signals. Also realtime can
be integrated if necessary (e.g. look rtime or the clock example in
the distribution)
but it is not necessary. You define what time means to you (e.g. it
could be the
sucession of server request, the sucession of mouse inputs, etc.).


* Primitives

The only events whose occurence can be generated by the client are
primitive events. The only signals whose values can be updated by the
client are primitive signals. All events and signals that result from
combinators are automatically updated by react. Primitives are the
mechanism to interface the reactive runtime described above with the
outside world (e.g. to define a keysym event).

Read now the "Primitive events and signals" section and come back
after ;

http://erratique.ch/software/react/doc/React#primitives


* Program structure

If you own your main loop I'd say you should more or less proceed as
follows.

1) Identify your primitives (sources of input). Define functions
to create primitives. Or if there's no dynamic creation of primitives
create them directly.

2) Define your reactive system in terms of primitive events and
combinators. Usually at the end of your dataflow graph you will have
effectful events and signals to give feedback to the world (beware,
primitive sending and update functions cannot not be used here to
feedback into the reactive system, depose the updates in a queue if
you really need to do that, but more likely you can use fix point
combinators to avoid that).

3) Define an infinite loop that sources the created primitives as
input is gathered.

4) Run the loop.

Have a look at the snippet of code at the end of this email. It
exposes the a subset of the sdl mouse as signals and events (note that
there's more than one way of exposing that information as a set of events
and signals, this may not be the best solution).

If you need to deal with callbacks then the result of callbacks can be
exposed as events and signals by creating a primitive and using the
primitive sending or updating function in the callback.

I now suggest you try to proceed to the "Basics" section of the
documentation

http://erratique.ch/software/react/doc/React#basics

After have a look at the "Semantics"

http://erratique.ch/software/react/doc/React#sem

You may also have a look at the breakout.ml example of the
distribution (use the version in the repository there are fixes for
xterm & vte based terminals).

http://erratique.ch/software/react/repo/test/breakout.ml


> I'm interested in whether FRP can be used to write Gtk interfaces with
> reduced code complexity.  Apparently it can, but I've no idea how.

Once you are more acquainted with the subject. Have a look at the
chapter 6 of Gregory Cooper's thesis [1] it provides strategies to
interface frp with existing object-oriented gui toolkits. Chapter 1
may also be interesting in motivating the frp approach.

Finally, feel free to ask any question,

Best,

Daniel

[1] http://www.cs.brown.edu/~greg/thesis.pdf

open React;;

module Mouse : sig
  val pos : (int * int) S.t                        (* mouse position signal. *)
  val up : unit E.t                             (* event on mouse button up. *)
  val down : unit E.t                         (* event on mouse button down. *)
  val input : unit -> unit         (* inputs mouse changes until user quits. *)
end = struct
  let pos, set_pos = S.create (0, 0)
  let down, send_down = E.create ()
  let up, send_up = E.create ()

  open Sdlevent;;
  let rec input () = match wait_event () with
  | QUIT -> ()
  | MOUSEMOTION e -> set_pos (e.mme_x, e.mme_y); input ()
  | MOUSEBUTTONDOWN e
  | MOUSEBUTTONUP e when e.mbe_button = Sdlmouse.BUTTON_LEFT ->
      (match e.mbe_state with PRESSED -> send_down () | RELEASED -> send_up ());
      input ()
  | KEYDOWN k when k.keycode = 'q' -> ()
  | _ -> input ()
end