The recent OCurrent 0.2 release included a little incremental library which might be interesting to some people. It is useful for writing programs that need to keep some computation up-to-date efficiently as the inputs change.
It is similar to the existing incremental and react libraries already in opam. Unlike
incremental (which pulls in the whole of
current_incr has no runtime dependencies (and build dependencies only on
current_incr immediately stops computations when they are no longer needed (rather than relying on weak references and the garbage collector).
It is a fairly direct implementation of the Adaptive Functional Programming paper, and might be a good starting point for people wanting to learn about that.
You can get the library using
opam install current_incr
Here’s a simple example (in utop):
#require "current_incr";; let total = Current_incr.var 10 let complete = Current_incr.var 5 let status = Current_incr.of_cc begin Current_incr.read (Current_incr.of_var total) @@ function | 0 -> Current_incr.write "No jobs" | total -> Current_incr.read (Current_incr.of_var complete) @@ fun complete -> let frac = float_of_int complete /. float_of_int total in Printf.sprintf "%d/%d jobs complete (%.1f%%)" complete total (100. *. frac) |> Current_incr.write end
This defines two input variables (
complete) and a “changeable computation” (
status) whose output depends on them. At the top-level, we can observe the initial state using
# print_endline @@ Current_incr.observe status;; 5/10 jobs complete (50.0%)
Unlike a plain
ref cell, a
Current_incr.var keeps track of which computations depend on it. After changing them, you must call
propagate to update the results:
# Current_incr.change total 12;; # Current_incr.change complete 4;; # print_endline @@ Current_incr.observe status;; 5/10 jobs complete (50.0%) (* Not yet updated *) # Current_incr.propagate (); # print_endline @@ Current_incr.observe status;; 4/12 jobs complete (33.3%)
Computations can have side-effects, and you can use
on_release to run some compensating action if the computation needs to be undone later. Here’s a function that publishes a result, and also registers a compensation for it:
let publish msg = Printf.printf "PUBLISH: %s\n%!" msg; Current_incr.on_release @@ fun () -> Printf.printf "RETRACT: %s\n%!" msg
It can be used like this:
# let display = Current_incr.map publish status;; PUBLISH: 4/12 jobs complete (33.3%) # Current_incr.change total 0; # Current_incr.propagate () RETRACT: 4/12 jobs complete (33.3%) PUBLISH: No jobs
A major difference between this and the react library (which I’ve used in previously in 0install’s progress reporting and CueKeeper) is that
Current_incr does not depend on the garbage collector to decide when to stop a computation. In react, you’d have to be careful to make sure that
display didn’t get GC’d (even though you don’t need to refer to it again) because if it did then the output would stop getting updated. Also, setting
0 in react might cause the program to crash with a division-by-zero exception, because the
frac computation will continue running until it gets GC’d, even though it isn’t needed for anything now.
Current_incr's API is pretty small. You might want to wrap it to provide extra features, e.g.
- Use of a
resulttype to propagate errors.
- Integration with
Lwtto allow asynchronous computations.
- Static analysis to render your computation with graphviz.
- Persistence of state to disk.
If you need that, consider using the main OCurrent library, which extends
current_incr with these features.