[ANN] Brr 0.0.1, a toolkit for programming browsers

Also tried Brr recently to write applications and some bindings and I have to say I think it is brilliant! With Brr writing bindings is as easy as reading the Js API and converting to types. The cookbook has everything you need to get up and running :))

I’ve tried pure js_of_ocaml in the past and didn’t enjoy it a lot (even though I wanted to), but loved the prospect of writing OCaml for the web. And by that I mean “idiomatic” OCaml. Brr (for me) has made this possible. Thank you @dbuenzli!

15 Likes

As a heavy user of React (have a 10k line native app written purely in React), I’m personally unsure of what aspects you are hinting at here. Could you elaborate?

I have only found a few aspects of the API that I don’t like - but mostly I’ve found fine alternative ways around them. If people are interested, I’ll probably make a blogpost at some point where I go through the “style” I have settled on - e.g. (up-/down-)sampling, top-level signals/events throughout, reactive first-class modules (containing/depending-on events/signals) etc.

Concerning ‘correct’ usage of FRP - the primary hack I needed in my codebase is the concept of ‘previous’ values ('a option) where earlier reactive nodes can observe future nodes previous value (so a way of keeping code liftet into the FRP monad but having potentially whole-program recursion). But I limit my use of this to a few places, and only within certain first-class module ‘chunks’ in the program so I don’t break compositionality. In a game-loop or in Elms update function, one has the whole previous state available - which this emulates, while gaining all the time-based programming advantages of FRP

1 Like

This is basically written here but mainly the following points:

  1. Signal initialisation. Despite being “unsafe” people do use S.value to initialize other signals because they rightly don’t want to pay the cost of a S.bind to initialize a signal with the current value of another. But unless you understand what you are doing this will likely lead to seemingly random failures at runtime. Too subtle and brittle.

  2. Fix points. I think people have a hard time wrapping their head around
    S.fix and E.fix. Especially if you have multiple recursive definitions. What you want here is a lazy infinitesimal delay combinator and plain let rec (this is not done yet in Note). I suspect that your “primary hack” is about this, since of what I understand that’s exactly what fix points give you, namely the previous value of your event/signal an infinitesimal amount of time ago.

  3. Reactive outputs as effectful signals and events. I think that mixing effects with FRP is a bad idea.

    A lot of people came to me asking how they could control the order of side effects in update cycles. But this is antithetic to the synchrony hypothesis which states that everything that occurs in an update step is simultaneous. Besides a lot of people want do this do it in order to feedback the reactive graph which is forbidden.

    Most of these effects deal with ressources on which you want precise life-time management. This means you want control over the updates of these effectful signals and events. You can use S.stop. But this breaks equational reasoning and given the previous point. I think it’s better to avoid effects in the reactive network and formalize its outputs via another structure (loggers in Note).

2 Likes

Yes I use that trick as well, and sparingly. But unless you initialize signals dynamically, you could avoid it by referring to the initial value of the dependency signal as a constant beside the signal.

I went through the {S|E}.fix annoyances in the beginning. As I think one of the strongest advantages of FRP is elegant and easy-to-read code, I settled on never using them. Instead I’ve written all the recursive parts of the graph as E.fold over events that sample their dependencies. E.g.:

let recorded_groups_s =
  let record acc (event, (tick, active_id)) = match event with
    | `Toggle_recording toggle ->
      if toggle then
        Some []
      else 
        let acc = acc |> CCOpt.to_list |> CCList.flatten in
        Some ((`Tick tick, None) :: acc)
    | `Active_group_id active_id ->
      acc |> CCOpt.map (fun recording ->
        (`Tick tick, Some active_id) :: recording
      )
  in
  let sampling = E.select [
    toggle_recording_e  |> E.map (fun toggle -> `Toggle_recording toggle);
    M_group.active_id_e |> E.map (fun id -> `Active_group_id id) 
  ]
  and sampled = S.l2 ~eq:C.Eq.never C.Tuple.mk2
      I.Frame.tick_s
      M_group.active_id_s 
  and init = None
  in
  S.sample C.Tuple.mk2 sampling sampled
  |> E.fold record init
  |> E.map (fun v -> v |> CCOpt.map T.Gseq.of_recording)
  |> S.hold init

No not directly - the problem is that I want most of my code written inside the FRP monad - so when I suddenly need a signal defined before another signal to depend on the dependent signals previous value, then I don’t want to ‘unlift’ all my code (which would also throw away all my coded time-semantics of the events/signals in between) and put it inside fold or fix. If I did this all the time, the program would converge towards being a simple game-loop in the end, where the previous world-state is given as argument to the game-loop, so every computation can observe the previous values of any other computation.

I agree that the programmer need to think before using effects in the FRP graph, but that doesn’t seem to change much with Note - except for making it explicit that you do it?

I find it simple to work with as I always push all effects to the edge of the graph (a major point of using FRP), and I would never define two events that were effectful which depended on the order of execution… One would just make a single event from the set of dependent events, and only be effectful there.

@rand I just want to clarify that I have no plans to stop maintaining React (it’s not as if pure OCaml code is hard to maintain :-).

But I want to try to improve the FRP experience (as well as give a better jsoo story for it) and find it difficult to do it in React itself.

2 Likes

@dbuenzli Love to hear that (:

I would be interested ;). I think I almost got our example below but a bit more explanation would be helpful. Fwiw, my current use of React also involves downsampling and logging via effectful events at the edges of the graph, but the graph is mostly static.

2 Likes