Sure thing. Let me back up and build up from the simple case to the most complex case.
The simple case
Consider the following code:
let%sub a = b in
c
The types are as follows:
a : 'a Value.t
b : 'a Computation.t
c. : 'b Computation.t
<entire expression> : 'b Computation.t
This is all dictated by the type of the sub
function, which is what the let-syntax code above expands to use.
val sub : 'a Computation.t -> f:('a Value.t -> 'b Computation.t) -> 'b Computation.t
The expansion of the let-syntax code above is
sub b ~f:(fun a -> c)
This should be nothing new if you’re familiar with let%bind
and let%map
from ppx_let
.
Destructuring
Now consider this code:
let%sub a, b = c in
d
The types are:
a : 'a Value.t
b : 'b Value.t
c : ('a * 'b) Computation.t
d : 'c Computation.t
Think about this for a few minutes; something should feel a bit off. Note that the pattern can actually be any pattern - triples, records, variants, you name it. But all the variables that show up in those patterns will have type _ Value.t
The reason this is possible is that let%sub
does some magic whenever it sees something other than a simple variable pattern. The above code expands to this:
let%sub temp = c in
let%sub a =
let%arr a, _ = temp in
a
in
let%sub b =
let%arr _, b = temp in
b
in
d
Why would you want this? Well, Bonsai is a layer on top of incremental, so the incrementality of this code is significant. If c
changes , then both projections will run, but only the components that have changed will continue to propagate changes.
Choice
Now we can get to match%sub
. Here’s some code similar to what I put in the first post, except the First
case contains a tuple now, to illustrate the destructuring point from the previous section.
let c (input : (int * int, int) Either.t Value.t) : _ Computation.t =
match%sub input with
| Left (x, y) ->
let%arr x = x
and y = y in
x + y
| Right x ->
let%sarr x = x in
x - 1
Again, all the variables in the patterns are of type _ Value.t
. For the same reason as with the let%sub
, the fact that we can pattern match is really weird; if all this expanded to was a call to sub
, we couldn’t pattern match because all we get from sub
is an opaque _ Value.t
. Yet somehow we’re able to explode the various components of the patterns. Here’s what the above code expands to.
let c (input : (int * int, int) Either.t Value.t) : _ Computation.t =
let%sub temp = input in
let%sub index =
let%arr temp = temp in
match temp with
| Left (_, _) -> 0
| Right _ -> 1
in
switch
~branches:2
~match_:index
~with_:(function
| 0 ->
let%sub x, y =
let%arr temp = temp in
match temp with
| Left (x, y) -> x, y
| Right _ -> assert false
in
let%arr x = x and y = y in x + y
| 1 ->
let%sub x =
let%arr temp = temp in
match temp with
| Left (_, _) -> assert false
| Right x -> x
in
let%arr x = x in x + 1)
Here’s what’s going on.
- First we project out a number indicating which case is the matching one. This projection runs any time any part of the data changes, but it’s cheap, and it will let us pick a subset of the data to care about.
- Next we call the
switch
function provided by Bonsai. This functions job is to call with_
ahead of time with all the numbers from 0 to branches
. Only one of those branches is active and alive and updating at one time. Whenever match_
changes, the active branch switches to its new value.
- Each branch projects out the variables in that branch’s pattern, similar to how we already saw in the previous section. The
assert false
should never get hit because it only gets hit in “impossible” conditions for each branch.
Clear as mud, right?