Tl;dr: Is there a way to automatically turn Incr.map ~f (Incr.map ~f:g x)
into a single incremental node with the same structure as Incr.map ~f:(Fn.compose f g) x
?
Context:
I’m working on an incremental evaluator for a small programming language. Very trimmed down, it looks like this:
module Incr = Incremental.Make ()
module Ast = struct
type t =
| Input of int Incr.t
| Incr of t
| Decr of t
| Condition of {
if_ : t;
then_ : t;
else_ : t;
}
end
let rec eval = function
| Ast.Input x -> x
| Incr t -> Incr.map (eval t) ~f:(( + ) 1)
| Decr t -> Incr.map (eval t) ~f:(( - ) 1)
| Condition { if_; then_; else_ } ->
Incr.if_
(Incr.map ~f:(Int.equal 0) (eval if_))
~then_:(eval then_) ~else_:(eval else_)
This is nice and works, we can create expressions, evaluate them, and see them react to their input changes:
let input_var = Incr.Var.create 0
let input = Incr.Var.watch input_var
let expr =
Ast.Condition
{
if_ = Incr (Incr (Decr (Decr (Input input))));
then_ = Input (Incr.return 1);
else_ = Input (Incr.return 42);
}
let eval_result = Incr.observe @@ eval expr
let _print_result_on_change : () =
Incr.Observer.on_update_exn eval_result ~f:(function
| Initialized new_value | Changed (_, new_value) ->
Format.printf "New result: %d\n" new_value
| _ -> ())
let set_input input_value =
Incr.Var.set input_var input_value;
Incr.stabilize ()
let () = set_input 1 (* Prints “New result: 42” *)
let () = set_input 0 (* Prints “New result: 1” *)
let () = set_input 1 (* Prints “New result: 42” *)
let () = set_input 2 (* Doesn't print anything because the result didn't change *)
However, the resulting incremental graph is much deeper than what I’d like, with a big tower of Map
nodes corresponding to the Incr
and Decr
Ast nodes, with a huge impact on memory usage (I didn’t measure for this toy language, but on the real one, up to 90% of the allocations are the incremental nodes).
What I would like instead is to be able to compress this graph into something like
where the Combined
node corresponds to Incr.map ~f:(fun x -> x + 1 + 1 - 1 - 1)
Is there any way to achieve that without changing the structure of the eval
function?