Ocaml React package tutorial

React is an Ocaml package made for reactive programming using Ocaml. The objective of this post is to illustrate and explain some examples of the React API. Therefore the basic concepts of React, such as Signals, Events and side effects, will not be repeated here. Below are some prerequisites for reading this post.

Most of the examples used in this post are inspired by test cases of React project. To report an issue in the post, contact me through my Github account. Thank you for your feedbacks.

Using React package

For compilation

ocamlfind ocamlopt -o clock -linkpkg \
    -package react clock.ml

For Ocaml Toplevel

#use "topfind";;
#require "react";;
open React;;
Module React.E
val once : 'a React.event -> 'a React.event

once e is e with only its next occurence. Any occurence after time t will not affect e.

1
2
3
4
5
open React (* omitted in the examples below *)
let w, send_w = E.create ()
let x = E.once w
let pr_x = E.map print_int x
let () = List.iter send_w [0; 1; 2; 3]

The output would be only 0. All the values after the first occurence are omitted.

val drop_once : 'a React.event -> 'a React.event

drop_once e is e without its next occurrence. Similarly, if we change the third line of the example above, the output would be 123. The reason is because the NEXT occurrence of e is omitted.

val app : ('a -> 'b) React.event -> 'a React.event -> 'b React.event

app ef e occurs when both ef and e occur simultaneously.

let f x y = x + y
let w, send_w = E.create ()
let x = E.map (fun w -> f w) w
let y = E.drop_once w
let z = E.app x y
let pr_z = E.map print_int z
List.iter send_w [0; 1; 2; 3]

The output would be 246. As explained before, drop_once makes y only take updates from the second occurrence onwards. Variable z takes the value of x when both x and y are updated.

val map : ('a -> 'b) -> 'a React.event -> 'b React.event

map f e applies f to e’s occurrences.

val stamp : 'b React.event -> 'a -> 'a React.event

stamp e v is map (fun _ -> v) e. It fixes the occurrence of e to v.

val filter : ('a -> bool) -> 'a React.event -> 'a React.event

filter p e are e’s occurrences that satisfy p.

val fmap : ('a -> 'b option) -> 'a React.event -> 'b React.event

fmap fm e are occurrences of e filtered and mapped by fm.

let v, send_v = E.create ()
let x = E.stamp v 1 (* x: 1111 *)
let y = E.filter (fun n -> n mod 2 = 0) v (* y: 02 *)
let z = E.fmap (fun n -> if n = 0 then Some 1 else None) v (* z: 1 *)
List.iter send_v [0; 1; 2; 3]
val diff : ('a -> 'a -> 'b) -> 'a React.event -> 'b React.event

diff f e occurs whenever e occurs except on the next occurence. In another words, it is triggered since the second the occurrence. Occurences are f v v’ where v is e’s current occurrence and v’ the previous one.

val changes : ?eq:('a -> 'a -> bool) -> 'a React.event -> 'a React.event

changes eq e is the occurrences of e with occurences equal to the previous one dropped. Equality is tested with eq (defaults to structural equality). The behavior is similar to Signals.

let x, send_x = E.create ()
let y = E.diff ( - ) x
let z = E.changes x
List.iter send_x [0; 0; 3; 4; 4]

The occurrences of y are 0310 (difference between current and previous one), while those of z are 034 (only changes are recorded).

val dismiss : 'b React.event -> 'a React.event -> 'a React.event

dismiss c e is the occurences of e except the ones when c occurs.

let x, send_x = E.create ()
let y = E.fmap (fun x -> if x mod 2 = 0 then Some x else None) x
let z = E.dismiss y x
List.iter send_x [1; 2; 3; 4; 5; 6]

The occurrences of y are 246 (even numbers). Occurrences of z are 135. When y has occurrence, z will not have any occurrence.

val until : 'a React.event -> 'b React.event -> 'b React.event

until c e is e’s occurences until c occurs.

let x, send_x = E.create ()
let stop = E.filter (fun s -> s = "c") x
let y = E.until stop x
let pr_y = E.map print_string y
List.iter send_x ["a"; "b"; "c"; "d"; "e"]

The output is ab. All the occurrences after stop event are dropped, although stop has no more new updates.

val accum : ('a -> 'a) React.event -> 'a -> 'a React.event

accum ef i accumulates a value, starting with i, using e’s functional occurrences. It takes the previous occurrence, applies to the function and evaluates the result as the current occurrence.

let f, send_f = E.create ()
let a = E.accum f 0
let pr_a = E.map print_int a
List.iter send_f [( + ) 2; ( - ) 1; ( * ) 2];
(* Output is 2 -1 -2 *)
val fold : ('a -> 'b -> 'a) -> 'a -> 'b React.event -> 'a React.event

fold f i e accumulates the occurrences of e with f starting with i. The behavior of fold is similar to List.fold_left.

let x, send_x = E.create ()
let y = E.fold ( * ) 1 x
let pr_y = E.map print_int y
List.iter send_x [1; 2; 3; 4]

The output would be 1 2 6 24.

val select : 'a React.event list -> 'a React.event

select el is the occurrences of every event in el. If more than one event occurs simultaneously the leftmost is taken and the others are lost.

let w, send_w = E.create ()
let x, send_x = E.create ()
let y = E.map succ w
let z = E.map succ y (* z and y are considered simultaneous *)
let t = E.select [w; x] (* t: 0 1 2 3 4 *)
let sy = E.select [y; z] (* sy: 1 3 4 5 *)
let sz = E.select [z; y] (* sz: 2 4 5 6 *)
send_w 0;
send_x 1;
List.iter send_w [2; 3; 4]

The expected occurrences of all variables are written in the comments. To know more about simultaneous events, read this.

val merge : ('a -> 'b -> 'a) -> 'a -> 'b React.event list -> 'a React.event

merge f a el merges the simultaneous occurrences of every event in el using f and the accumulator a. You may refer to the previous API to check the meaning of simultaneous events.

let w, send_w = E.create ()
let x, send_x = E.create ()
let y = E.map succ w (* y is a simultaneous event w.r.t. w *)
let z = E.merge (fun acc v -> v::acc) [] [w; x; y]
send_w 1;
send_x 4;
send_w 2;

The occurrences of z are [2; 1], [4], [3; 2]. Hint: fun acc v -> v::acc pushes every v to the accumulator acc.

val switch : 'a React.event -> 'a React.event React.event -> 'a React.event

switch e ee is e’s occurrences until there is an occurrence e’ on ee, the occurrences of e’ are then used until there is a new occurrence on ee, etc.

val fix : ('a React.event -> 'a React.event * 'b) -> 'b

fix ef allows to refer to the value an event had an infinitesimal amount of time before.

Module React.S
val hold : ?eq:('a -> 'a -> bool) -> 'a -> 'a React.event -> 'a React.signal

hold i e has the value of e’s last occurrence or i if there was not any.

1
2
3
4
let x, send_x = E.create ()
let a = S.hold 0 x
let pr_a = S.map print_int a
List.iter send_x [1; 2; 3; 3; 3; 4; 4; 4]

A 0 will printed out at line 3 because x has no occurrences by then. Then 1234 will be printed. Note that only updates that change the value of a signal are printed.

Many API are similar to those in React.E. Therefore this tutorial jumps straight to Lifting combinators. Lifting transforms a regular function to make it act on signals. For examples, l2 takes a function with two arguments as its input.

val l2 : ?eq:('c -> 'c -> bool) ->
    ('a -> 'b -> 'c) -> 'a React.signal -> 'b React.signal -> 'c React.signal

Example:

let f x y = x mod y
let fl = S.l2 f
val fl : int React.signal -> int React.signal -> int React.signal = <fun>
Some other examples