Search code examples
ocamlinteropffibucklescript

Define a closure variable in buckle script


I'm trying to convert the following ES6 script to bucklescript and I cannot for the life of me figure out how to create a "closure" in bucklescript

    import {Socket, Presence} from "phoenix"

    let socket = new Socket("/socket", {
      params: {user_id: window.location.search.split("=")[1]}
    })

    let channel = socket.channel("room:lobby", {})
    let presence = new Presence(channel)

    function renderOnlineUsers(presence) {
      let response = ""

      presence.list((id, {metas: [first, ...rest]}) => {
        let count = rest.length + 1
        response += `<br>${id} (count: ${count})</br>`
      })

      document.querySelector("main[role=main]").innerHTML = response
    }

    socket.connect()

    presence.onSync(() => renderOnlineUsers(presence))

    channel.join()

the part I cant figure out specifically is let response = "" (or var in this case as bucklescript always uses vars):

    function renderOnlineUsers(presence) {
      let response = ""

      presence.list((id, {metas: [first, ...rest]}) => {
        let count = rest.length + 1
        response += `<br>${id} (count: ${count})</br>`
      })

      document.querySelector("main[role=main]").innerHTML = response
    }

the closest I've gotten so far excludes the result declaration

...
...

let onPresenceSync ev =
  let result = "" in
    let listFunc = [%raw begin
        {|
          (id, {metas: [first, ...rest]}) => {
            let count = rest.length + 1
            result += `${id} (count: ${count})\n`
          }
        |}
      end
    ] in
      let _ =
        presence |. listPresence (listFunc) in
          [%raw {| console.log(result) |} ]
...
...

compiles to:

function onPresenceSync(ev) {
  var listFunc = (
          (id, {metas: [first, ...rest]}) => {
            let count = rest.length + 1
            result += `${id} (count: ${count})\n`
          }
        );
  presence.list(listFunc);
  return ( console.log(result) );
}

Solution

  • result is removed as an optimization beacuse it is considered unused. It is generally not a good idea to use raw code that depends on code generated by BuckleScript, as there's quite a few surprises you can encounter in the generated code.

    It is also not a great idea to mutate variables considered immutable by the compiler, as it will perform optimizations based on the assumption that the value will never change.

    The simplest fix here is to just replace [%raw {| console.log(result) |} ] with Js.log result, but it might be enlightening to see how listFunc could be written in OCaml:

    let onPresenceSync ev =
      let result = ref "" in
      let listFunc = fun [@bs] id item ->
        let count = Js.Array.length item##meta in
        result := {j|$id (count: $count)\n|j}
      in
      let _ = presence |. (listPresence listFunc) in
        Js.log !result
    

    Note that result is now a ref cell, which is how you specify a mutable variable in OCaml. ref cells are updated using := and the value it contains is retrieved using !. Note also the [@bs] annotation used to specify an uncurried function needed on functions passed to external higher-order functions. And the string interpolation syntax used: {j| ... |j}