Skip to content
Fatemeh H edited this page Jul 10, 2018 · 5 revisions

Pendulum's API

The syntax extension code entry

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.

Quick example

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

Interface with js_of_ocaml

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)