Let%map ... and ... and -> let ... in let ... in let ... in

I see a

let%map f = ...
and g = ...
and h = ... in

which has no cycles; so I am trying to rewrite it as a:

let%map f = ... in
let%map g = ... in
let%map h = ... in

or a

let%map f = ... in
let g = ... in
let h = ... in

but failing on both accounts. Is this transform possible? The snippet of code is:


  let%map apply_action =
    let%map counter = model >>| Model.counter in
    fun (Increment : Action.t) _ ~schedule_action:_ -> { Model.counter = counter + 1 }
  and view =
    let%map counter =
      let%map counter = model >>| Model.counter in
      Vdom.Node.div [ Vdom.Node.text (Int.to_string counter) ]
    in
    Vdom.Node.body [ counter ]
  and model = model in

which comes from: incr_dom/app.ml at master · janestreet/incr_dom · GitHub

Thanks!

1 Like

That’s not possible in general. If the underlying thing is monadic, you can rewrite it as

let%bind f = ... in
let%bind g = ... in
let%map h = ... in

But that’s not a given. If the thing in question is applicative but not monadic, you have to use a single let%map ... and ... (or let+ … and+) even if the bindings seem to be independent. (a way to look at this is that since the only way to get several bindings is let%map ... and, the bindings have to be independent).

1 Like
  1. I still don’t understand this.

  2. Maybe type signatures will explain why we can’t “untangle” this. Is the following accurate:

let%map id_a : 'a = expr_of_type[ 'a t ]
and id_b : 'b = expr_of_type[ 'b t ]
and id_c : 'c = expr_of_typpe[ 'c t ]
in expr_of_type 'd // this expr can use id_a, id_b, id_c
// overall expr returns obj of type [ 'd t ]

Have you seen this which shows the way that the ppx_let rewrites expressions? I’ll paste it in here for reference.

let%bind P1 = M1 and P2 = M2 and P3 = M3 and P4 = M4 in E

let%map  P1 = M1 and P2 = M2 and P3 = M3 and P4 = M4 in E

that expand into

let x1 = M1 and x2 = M2 and x3 = M3 and x4 = M4 in
bind
  (both x1 (both x2 (both x3 x4)))
  ~f:(fun (P1, (P2, (P3, P4))) -> E)

let x1 = M1 and x2 = M2 and x3 = M3 and x4 = M4 in
map
  (both x1 (both x2 (both x3 x4)))
  ~f:(fun (P1, (P2, (P3, P4))) -> E)

Check it out, the incremental type does provide a monadic interface (let syntax bind). So you can rewrite it like emillon shows and get the types to work out:

Original example:

let create :
    Model.t Ui_incr.Incr.t ->
    old_model:'a ->
    inject:'b ->
    (Action.t, Model.t, 'c) Component.t Ui_incr.Incr.t =
 fun model ~old_model:_ ~inject:_ ->
  let open Incr.Let_syntax in
  let%map apply_action =
    let%map counter = model >>| Model.counter in
    fun (Increment : Action.t) _ ~schedule_action:_ ->
      { Model.counter = counter + 1 }
  and view =
    let%map counter =
      let%map counter = model >>| Model.counter in
      Vdom.Node.div [ Vdom.Node.text (Int.to_string counter) ]
    in
    Vdom.Node.body [ counter ]
  and model = model in
  (* Note that we don't include [on_display] or [update_visibility], since
     these are optional arguments *)
  Component.create ~apply_action model view

written monadically

let create' :
    Model.t Ui_incr.Incr.t ->
    old_model:'a ->
    inject:'b ->
    (Action.t, Model.t, 'c) Component.t Ui_incr.Incr.t =
 fun model ~old_model:_ ~inject:_ ->
  let open Incr.Let_syntax in
  let%bind apply_action =
    let%map counter = model >>| Model.counter in
    fun (Increment : Action.t) _ ~schedule_action:_ ->
      { Model.counter = counter + 1 }
  in
  let%bind view =
    let%map counter =
      let%map counter = model >>| Model.counter in
      Vdom.Node.div [ Vdom.Node.text (Int.to_string counter) ]
    in
    Vdom.Node.body [ counter ]
  in
  let%map model = model in
  (* Note that we don't include [on_display] or [update_visibility], since
     these are optional arguments *)
  Component.create ~apply_action model view

I guess there probably is a reason for writing it with the first way with the and, though I can’t find it on the docs.


By the way…I see you ask a question about incremental, and now incr_dom…as far as I can tell, bonsai has pretty much replaced incr_dom as the choice for new projects.

Thanks for sharing; bonsai looks very interesting. I’m going to work through a few more incr_dom models out of curiosity, then I’ll look into bonsai.

The problems that bonsai claims to solve in https://github.com/janestreet/bonsai/blob/master/docs/blogs/history.md – I have not run into them yet [or if I have run into them, I’m not aware they’re the problem described in that doc].

By the way, I saw this from Ty Overby in an Incr_dom GitHub issue.

…Bonsai is built on top of Incr_dom, so changes to Incr_dom nowadays are largely going to be due to requirements for Bonsai. Incr_dom is maintained, but also quite stable. Most of the improvements that I’ve wanted to make it were of the form “this boilerplate shouldn’t be necessary if the right abstractions were in place”, which is what the Bonsai project is all about.

1 Like