Simple OCaml <-> js interaction error

Definition of Msg_js:


module Msg_Js = struct
  class type jst =
    object
      method tag_int : int Js.readonly_prop
      method msg : Js.Unsafe.any Js.readonly_prop
      method transfer_list : Js.Unsafe.any Js.js_array Js.t Js.readonly_prop
    end


Print statement:

              let untyped_msg : Msg_Js.jst Js.t = untyped_msg in
              Firebug.console##log
                (object%js
                   val error_msg = "main_obj: unknown msg type"
                   val msg = untyped_msg
                   val tag_int_js = untyped_msg##.tag_int
                end)

Actual output: (EDIT: pasted wrong output last time)

{error: 'main_obj: unknown msg type', msg: {…}, tag_int: undefined}

  error : "main_obj: unknown msg type"

  msg : 
    msg : {url: '/iframe_gfx.html', port: MessagePort}
    tag_int : 5
    transfer_list : []
  [[Prototype]] : Object

  tag_int : undefined
[[Prototype]] : Object

My confusion is that in the untyped_msg print out, we clearly see tag_int: 5

However, when we do untyped_msg##.tag_int, we get tag_int: undefined

What am I doing wrong here ?

Issue resolved. Turns out to be another consequence of jsoo object%js ... end notation removing everything after the lat “_” in a field name.

Is there any documentation on this? I’ve also ran into it and was utterly confused.

https://ocsigen.org/js_of_ocaml/latest/manual/library

Some overloading is possible using a syntactic trick: names _foo, foo_abcd and foo are all mapped to a same Javascript field name foo: when accessing a field of an object, the name given in the OCaml code is transformed by removing a leading underscore and then removing all characters starting from the last underscore; this yields the corresponding Javascript name. For instance, these three types correspond to the same Javascript method drawImage:

1 Like

I also was quite confused about the js_of_ocaml js interface. Did not manage to achieve what i wanted and in the end I gave up and tried Brr instead. It was a much more pleasant experience. No ppx magic and the documentation is quite good.

In my experience:

  • Brr is an opinionated “ocaml”-ish wrapper of parts of the JS Chrome API
  • Js_of_ocaml/lib is more of a “mechanical” translation of parts of the JS Chrome API
  • Neither set of bindings is complete
  • Js_of_ocaml/lib is easier to extend when I have to use some JS api not provided by the lib

In my case I was trying to interact with the Cloudflare worker runtime environment. So no prebuilt library was provided, I had to use the Brr FFI. Thanks to the documentation with all the examples, it was mostly a breeze.

I never got the hang of Brr; anytime I was trying to convert a JS snippet to Brr, the provided API never quite matched up with the MDN docs; I never found a good way to determine if (1) the API was not in Brr and I should bind it myself or (2) the API was in Brr, but I was not finding it.

A few JavaScript APIs were remixed because they were too broken to work with, mostly the old DOM stuff.

However before people start making wrong inferences from your comments I’d like to point out that most of the time in Brr:

  1. There is a one to one correspondance between Brr’s representation and browser APIs according to this naming convention. Sometimes a few API prefixes are factored out in a module e.g. (MIDIInput → Midi.Input).

  2. This representation is thoroughly documented by linking into MDN. Each type representing a JavaScript object documents which object it represents with a link on its definition on MDN. Each function acting on a type has a direct link on the corresponding functionality it binds to on MDN.

  3. There is an index of which browsers APIs are bound.

But of course to benefit of all that you need to read the docs.

Exhaustiveness is an explicit non-goal of the project. As far as browsers API go there’s a ton of outdated things you should not use. It’s one of the goal of brr to save your time by not binding to the useless stuff.

6 Likes

If I understand your post correctly, you are talking about Brr doc linking to MDN doc.

I am interested in the inverse problem: given a MDN class/function name, how easy is it to guess the corresponding Brr function to use ? This is the problem I run into all the time when manually porting from JS to OCaml.

I would argue that Brr is not optimized for this at all. There seems to be many naming quirks that makes sense for Brr internally but makes it hard to go from (JS Class, JS method) to corresponding Brr function. Examples of this include:

  • Brr, Brr_canvas, Brr_io, Brr_webaudio, Brr_webcrypto, Brr_webgpu, Brr_webmidi, Brr_webworkers split. (This feels arbitrary from MDN docs)

  • URL is renamed Uri

  • WebWorkers.postMessage is renamed post

  • other small inconsistencies I can’t recall right now

Individually these are small quirks; from the perspective of “here’s a blob of JS code, rewrite it in OCaml”, these mental “pebbles” largely prevented me from ever getting into “flow state” when translating JS to Brr.

Something else: in my opinion (and I may be entirely wrong here), I think the Jsoo “class” based technique works better than a “functional” approach for the following reason.

Suppose we have:

1.  a JS line that looks like:
obj.foo(arg1, arg2, arg2);

2. we know that the type of obj is Orange

that we want to translate to OCaml.

In the case of Brr, I’m not sure how to grep for this function ‘foo’ – I need to guess which module corresponds to type Orange ; then a function that is named “foo”-ish ; but things may not be arranged this way as matching MDN layout is not the highest objective

In the case of Jsoo, I grep for “class type [guess based on Orange]”, then after I find it, I get something of the form: [1]

class type orange = object

  method ... = Js.readonly_prop / Js.method
end

and all the methods are there in one place, and I find the one closest to ‘foo’.

From the perspective of not reading the docs, the layout of the code and the use of classes makes it much easier to guess/find the right function name to use.

[1] In the class case, I don’t even have to guess, I can just do:

let obj: () = ...

and the compiler error will tell me approximately the correct class type of obj

The already mentioned index provides a map to that. MDN docs are also quite arbitrary regarding this.

In general new large well delineated APIs get their own namespacing module. For the rest I wanted to avoid to have to open dozens of modules. The state of Jsoo separatate compilation when this was done also likely got taken into account.

I’m not claiming this is the best way of distributing, but then no one proposed something better.

The URL JavaScript module is totally broken. Uri is named that way because it works differently from URL, that’s mentioned in the docs of course.

That’s likely a mishap, I bound thousands of entry points during different, long and boring periods. I don’t guarantee there isn’t a few occurences where I got distracted from the discipline or that the whole thing is totally consistent. This could have been reported.

Since it seems you are mainly interested in translating JavaScript code as OCaml then you are right that’s exactly what jsoo’s approach give you: the ability to write typed and idiomatic JavaScript code in OCaml with unscrutable type error messages.

Personally I’m interested in writing OCaml code, not JavaScript, and that’s the reason why Brr had to be devised.

1 Like

I think this might be a misunderstanding of Brr and JSOO. Jsoo, in general, encourages mixing JS values in your Ocaml code, sorting of two languages meeting as one. From my usage of Brr, it is an FFI, which means it’s meant to be used only on the boundary. So your program is almost entirely “real” Ocaml values, and at the edges, where it interacts with JS, you use Brr to translate. This is simlar to how FFI works in most other situations, for example consider when you call C functions from Ocaml.

I do think this is the core if the issue. With Brr, you seem to be doing for:

[1: JS] ↔ [2: idiomatic OCaml]

whereas I’m after

[3: JS] ↔ [4: stupid/dumb/mechanical OCaml] ↔ [5: cleaner OCaml API]

Atleast to me, [4] is much easier to write than [2].