-
Notifications
You must be signed in to change notification settings - Fork 1
The syntax extension in called from OCaml code by tagging a let
with %sync
let%sync my_machine = (* pendulum program *)
my_machine
is now a function with one parameters corresponding to a tuple of
initial values for inputs arguments.
let (* ... *), step = my_machine ( ... )
Calling my_machine ( ... )
will create one particular instantiation
of this reactive program description, represented by a couple of two values :
- first one is the tuple of setters of inputs arguments :
- the second one is the step (moving forward or run) function which executes one instant of the reactive program instatiation.
open Pendulum
open Machine
let%sync hw_loop =
loop begin
atom (print_string "Hello world\n");
pause
end
defines a reactive program which is printing a string and waiting for the next instant, and so on. Now I want to execute it :
let () =
let step = hw_loop () in
for i = 0 to 9 do
ignore (step ())
done
We get the step
function from the instantiation (hw_loop
has no params). Then we call it in a for
loop to trigger the instant 10 times.
Now you would like to add input signals to your machine. So you can give it parameters at each instants. First, modify your reactive program this way, by defining inputs signals :
let%sync hw_loop =
input s1;
input s2;
(* ... *)
Then you must pass signal initial values to the instantiation call and get back the setters functions for inputs :
let () =
let (set_s1, set_s2), step = hw_loop ((), ()) in
(* ... *)
You may want to play with your inputs in the reactive program. We use the reactive
statement present
which tests if the signal has been emited (or set present
in the OCaml world) during this instant. If so, it executes the corresponding
alternative. As you can see, you only have to write one alternative is the
second does nothing
.
let%sync hw_loop =
input s1;
input s2;
loop begin
present s1
(atom (print_string "Hello"));
present s2
(atom (print_string "World\n"))
(atom (print_string "\n"));
pause
end
And eventually call the step
in the for loop :
let () =
let set_s1, set_s1, step = hw_loop ((), ()) in
for i = 0 to 9 do
if i mod 2 = then set_s1 () (* s1 is present only on even i *)
else set_s2 (); (* s2 is present only on odd i *)
if i mod 5 = 0 then set_s1 (); (* if i is a multiple of 5 then both are presents *)
ignore (step ())
done
In the state above, the only way to connect a reactive program to js events, is to call the step function
in the callback. For example, the following program take a textarea
element and a int * int
in parameter.
let%sync machine =
input checked, unchecked, intext;
output reset;
loop begin
trap t begin (
await checked;
await intext;
atom (print_endline !!textin);
exit t;
) || loop (present unchecked (exit t); pause)
end;
emit reset () ; pause
end
Then we must write the following boilerplate code
let (set_checked, set_unchecked, set_request, get_reset), react =
machine ((), (), "", ())
in
checkbox_accept##.onclick := handler (fun ev ->
if Js.to_bool checkbox_accept##.checked then
set_checked ()
else
set_unchecked ();
ignore @@ react (); Js._true);
request_button##.onclick := handler (fun ev ->
set_textin (Js.to_string @@ text_content##.value);
ignore @@ react (); Js._true);
);
The first statement is an obligation since it initialize the reactive program. However, in the second statement I always say the same thing : set the new signal value and call the step function. This part can be easily generated. That's why pendulum proposes a syntax to automatically bind a Javascript event to a signal.
First of all, we have to replace input signals checked
and unchecked
by a
Javascript checkbox cb
. We replace the text input signal by the js element
field
. We can now use the syntax elt##event
to test the presence of a signal
binded to the event event
on the element elt
. The operator ##
comes from
js_of_ocaml.
let%sync machine =
input (cb : inputElement Js.t);
input (sub : buttonElement Js.t);
input (field : inputElement Js.t);
output reset;
loop begin
trap t begin (
await (cb##onclick & cb##.checked = Js._true); (* wait for a check *)
await sub##onclick; (* wait for a check *)
!(Dom_html.window##alert (!!field)##.value);
exit t;
) || loop (present (cb##onclick & cb##.checked = Js._false) (exit t); pause)
end;
emit reset () ; pause
end
Writing cb##onclick
generates the code that modify the element cb
with a
callback for the event onclick
. This callback sets the signal as present in
the next instant and triggers the reaction function :
cb##.onclick := handler (fun ev ->
set_cb_onclick ();
ignore @@ react ();
Js._true);
This code is executed when the initialization machine
is called. So we don't need
to write the previous boilerplate code to bind signals to events.
So the initialization is only :
let main _ =
let _ = machine (my_checkbox, my_button, my_text_content, ()) in
Js._false
let () = Dom_html.(window##.onload := handler main)