0

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) );
}
glennsl
  • 28,186
  • 12
  • 57
  • 75
Sampson Crowley
  • 1,244
  • 1
  • 9
  • 22

1 Answers1

1

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}

glennsl
  • 28,186
  • 12
  • 57
  • 75
  • Great! yeah, I know that raw is to be avoided, for exactly the reason I posted. I'm still new to OCaml and Bucklescript. I was not aware of `ref`, thanks! as a side question, what would be the equivalent of destructuring an array or object? (example js: `{ metas: [first, ...rest] } = obj` – Sampson Crowley Dec 04 '19 at 17:09
  • 1
    There isn't really an equivalent, because patterns in OCaml are static and don't create new values. They just bind to parts of an existing value. So you can have a pattern such as `[|a, b|]` which matches a two-element array, but you can't have one that requires copying elements of the old array into a new one. You also can't destructure objects because they're dynamic as well. – glennsl Dec 04 '19 at 18:31
  • 1
    What you'd usually use instead are records and lists, which work exactly like you'd expect here. Unfortunately that doesn't help with JavaScript interop because JavaScript doesn't have records and lists,it has objects and arrays. So unfortunately you just have to do it the old-fashined way. – glennsl Dec 04 '19 at 18:33