JS_of_OCaml vs Bucklescript

I’ve had a nice experience with js_of_ocaml (compiling F* to JS, demo at https://people.csail.mit.edu/cpitcla/fstar.js/stlc.html). Performance tuning was a bit time consuming, but otherwise it worked well. The documentation is fairly decent too. The authors are pretty responsive on Github. One of F*'s dependencies, ZArith, is written in C, so I had to write a JS version of it and tell js_of_ocaml about it using //Provides: annotations (see http://ocsigen.org/js_of_ocaml/3.1.0/manual/linker).

I tried using Bucklescript to compile the same program, but I failed. I couldn’t find a way to make Bucklescript use my JS version of ZArith (instead, the compiler just complained about not supporting ZArith; see https://github.com/BuckleScript/bucklescript/issues/1744 for more info, though the issue was closed without a solution). This was a year ago, so things might have gotten better?

Also worth mentioning: the latest js_of_ocaml supports reason syntax.

1 Like

Is your Javascript Zarith available somewhere? It would be quite useful for us too…

1 Like

It’s at https://github.com/cpitclaudel/fstar.js/blob/master/src/js/zarith.js. It includes everything I needed to support F*, which is a good fraction of the library, but not all of it. It should be easy to add the rest, but I haven’t had the time or need.

3 Likes

Thanks Yaron!

gen_js_api currently only supports js_of_ocaml, but it is indeed a deliberate decision to use a binding model which should make it relatively straightforward to use the same specifications with Bucklescript, with a very light adaptation of the binding generator (or just the runtime library, perhaps).

FWIW, most LexiFi’s front-end web development is done with js_of_ocaml, but using only its compiler, not its standard library nor its FFI syntax extension. This “feels” even more OCaml-ish than “usual” js_of_ocaml code, I’d say.

See https://github.com/LexiFi/ocaml-vdom/blob/master/lib/js_browser.mli for a partial binding to usual browser APIs with gen_js_api.

Having played for some time with those Js_browser bindings, I want to say they are really cool to use (vdom is also very nice, btw). How about releasing them in opam? (maybe independently of vdom)

Since there are enough discussions, I want to add several missed items:

  1. We started working on upgrading the compiler against 4.06.1, it may take a while.
  2. It is not true that JSOO preserves more OCaml semantics than BuckleScript (it does preserve more undefined behavior), core works with JSOO mostly because the author also works in the same company…
  3. JSOO is indeed more friendly to OCaml experts since it does not take over your build system, BuckleScript would take over your build system since we need generate NPM style modules

If you have some background in JS, I would suggest you give BuckleScript a try

3 Likes

That’s not why jsoo worked for us! The current maintainer (not author) of jsoo does work at Jane Street, but jsoo worked with Async and Incremental the first time, without modification. Hugo has done great work improving things (like adding separate compilation), but not for this purpose, as far as I know.

Whether you decide to call some unsafe subsets of OCaml as “undefined behavior” is an interesting point. My view is that slices of this undefined behavior actually needs to be defined more explicitly and given clear semantics, because safe languages need unsafe subsets. Just consider the OCaml code that’s generated by Coq, which is packed full of Obj.magic! Or the design of Zarith, which takes very specific advantage of the Obj module for performance optimization. Rust has taken a similar turn of trying to define its unsafe subset more precisely, and I believe OCaml should do the same.

Anyway, as a practical matter, it’s awfully convenient that jsoo preserves OCaml’s behavior quite faithfully in these aspects as well.

y

8 Likes

It’s not true that jsoo preserve all undefined behavior(or unsafe subset), maybe it covers some subset where core happens to used.
JSOO is an amazing piece of software that I have full respect(and I still use it), but we also put great effort to make OCaml accessible to average JS dev. Actually why I made BuckleScript is because I failed to sell JSOO to my last employer, it is very hard to sell it in a company whose codebase is not mostly OCaml.

Edit: why it is hard to sell is that JSOO needs developers to go all in on the OCaml technology(which is great but hard to convince your manager) while BuckleScript provides an incremental migration path. It is an amazing story that Janestreet adopted OCaml in most of his codebase, but it is not the case for most other companies, sadly.

Edit: I understand that BuckleScript may not appeal to some hard core OCamler’s taste, but it is a great addition to the OCaml ecosystem, there is almost no competition between BuckleScript and JS_of_OCaml, one is targeted to audience who cared about JS and incremental migration is a must while the other is targeted to audience who already got engaged in OCaml. It is something that we really should be proud of that we have two high production quality JS compilers, I do not know of a second language that has such luxury.

17 Likes

I think it’s better than that indicates. It of course doesn’t capture everything, but it gets a surprisingly large subset. Indeed, while I’m sure they exist, I can’t think of a principled use of Obj.magic that fails under jsoo. Do you have any examples in mind?

I admit that at first I didn’t see the point at all, but it does seem that Bucklescript fits in much more easily for people with a largely JavaScript toolchain. I think the only unfortunate thing about the jsoo/bucklescript split is that bindings now have to choose which one they support. It would be great if we could move towards tools that could support both. Maybe gen_js_api could be come such a tool…

That’s of course true, but it’s worth noting that you don’t have to have a complete OCaml toolchain to use jsoo effectively. You can definitely call ordinary JavaScript from jsoo code, and I believe the reverse is pretty easy as well. It seems like the key advantage of Bucklescript is that it generates readable JavaScript, which jsoo definitely doesn’t!

3 Likes

Side note. I think it’s quite unfortunate that Coq generates that sort of code. I’m not sure I understand why it’s necessary, but I’ve never understood the internals of extraction very well.

It would be great to have a more thorough comparison between the two tools. Having used JSOO as part of Eliom and having vaguely observed Bucklescript from afar in Reason, I’d love to know what the actual differences are. I know BS is trying to use native JS types as much as possible, which isn’t the case in JSOO, and that BS’s conversion starts post-typechecker, while JSOO converts Lambda AST. Is FFI easier in BS?

Also, @bobzhang, it was really disheartening for me to learn that BS added the new |. operator. It’s syntactic sugar that makes it very hard to convert BS to OCaml code compatible with the rest of the ecosystem – you’d essentially have to use camlp4. I think these things need to be weighed very heavily before they’re added, as they slowly turn BS into an incompatible fork of OCaml.

BS’s conversion starts post-typechecker, while JSOO converts Lambda AST

AFAIU, BS’s conversion indeed starts post-typechecker using the Lambda intermediate representation as source (which is internal to the compiler). Jsoo starts from the bytecode produced by ocamlc (a bit later in the pipeline).
This is why js_of_ocaml doesn’t need to exist as a compiler fork, and is a lot less tightly bound to the compiler’s implementation (since ocaml’s bytecode is quite stable).

The addition of the |. and the direction of Belt and Reason are indeed heading towards a fork which is very concerning. It’s unfortunate that I can’t build React apps in OCaml using the OCaml ecosystem.

It is a normal PPX(provided by the compiler and a flag to turn it off).

The belt library is a highly optimized library for JS backend, it is up to the user decide to use it or not, note currently BuckleScript already did a very good job with regard to the added JS size, belt tried to deliver the optimal experience for large scale UI productions.

Here is a tldr story that explained why we introduced belt, people told me that they can not use ocaml stdlib map, since the internal monitor tools showed it brings more than 10K bytes in the JS payload, after we introduced belt, the performance is better, and the added size is reduced to 1K bytes, and people are happy about it.

2 Likes

Think about the representation of float. It has quite different representations

I think this is an approach which prevents all improvements in writing code. I have a legacy CoffeeScript library and it generates some pretty readable JavaScript code, to the point where I get pull requests that modify the JavaScript code. It got a little bit better since the CoffeeScript compiler added a note that the generated source is explicitly not the the canonical source code. If the generated code is the canonical hackable source, what is even the point of using a compiler if you can only do the compilation step once before you start editing the generated code, thus a second compilation would just erase all your changes.

The only times I have found the readable output of CoffeeScript useful is to learn how its syntax works, because it was easier to write something and see what it compiles to instead of actually learning the syntax properly. Which probably speaks more about the ambiguous syntax of CoffeeScript than anything else.

With OCaml it is still pretty alright, because the semantics are still pretty close to JavaScript, so generating readable source code is attainable. But if you start to use compilers for PureScript or Eta I could imagine that e.g. compiling lazy semantics would lead to JavaScript code that is everything but readable, because you sort-of implement your own semantics on top of JavaScript.

What worries me is that BuckleScript is still stuck on 4.02.1, a version that seems to be on its way out in the general OCaml community where it is not uncommon to see 4.03 as minimal version, so over time less and less existing code will be compatible. And despite announcements of rebasing to newer versions, so far to my knowledge this hasn’t happened any single time. It would be required to be done either pretty often, like on every new upsteam release or included in the mainline compiler. Rebasing is hard, which is why I suppose multicore will be merged, to keep it from bitrotting. I fear we are seeing a fork that might go the same way as MetaOCaml or JoCaml before.

So in my opinion BuckleScript is more an OCaml-like language, possibly with a JavaScript-like syntax (Reason) aimed at the same market as Elm, not aimed at OCaml developers who would like to avoid having to write JavaScript.

6 Likes

So in my opinion BuckleScript is more an OCaml-like language, possibly with a JavaScript-like syntax (Reason) aimed at the same market as Elm, not aimed at OCaml developers who would like to avoid having to write JavaScript.

Keep spreading such FUD makes me feel really sad, we are working really hard to bring OCaml to more commercial use cases, BuckleScript definitely aims to be 100% honest to OCaml language semantics and its type system.
Js_of_Ocaml has been around quite a long before BuckleScript’s existence, if you think js_of_ocaml is the right approach to attract more audience, it should have already happened. I don’t think there is competition between each other, we are solely bringing more people.

12 Likes

To me, the unfortunate part is that BuckleScript is maintained outside of the mainline of OCaml as a fork. If it was maintained inside the same infrastructure and always up to date with the latest release, it would be less problematic to me for there to be two JS backends.

Having worked on a small BuckleScript/ReasonReact app full time for two weeks, my experience is mixed.

On the one hand, the experience with BuckleScript is quite smooth. The build system, the standard library (Belt), built-in syntax extensions, libraries like ReasonReact, all work well together and present a compelling package. They are built with a lot of care and attention to details.

On the other hand, BuckleScript makes it very hard to reuse any existing code. (Not a lot of people have the luxury of having this problem, though!) You can’t just use an opam package; you need to convert it to an NPM package first. Can’t use your favorite ppx. Can’t share async code between native and JS.

While with js_of_ocaml you can take any package from opam, use any ppx, use Lwt on both client and server. This is very compelling for me.

My impression is that, while BuckleScript is faithful to OCaml, the language, it forks the OCaml ecosystem. And that’s not necessarily a bad thing. Existing OCaml ecosystem has a huge survivorship bias. Just compare what is being worked on in the “new world” (BuckleScript/Reason) and the “old world”. The old world is addressing the issues of the existing OCaml users, big and small. While the new world is focused users, who are yet to be OCaml developers. The new world is working at the beginning of the acquisition funnel, while the old world—with the end of it.

So I plan to stick to the old world myself but will keep promoting both the old-world as well as BuckleScript, Reason, and Elm—depending on who I talk to.

13 Likes

An argument that may be made (I don’t know what kind of mind mapping these readable outputs gives in practice) is that you might get reasonably readable information when you enter the JavaScript debugger.

While I was in totally in awe when I called Js.debugger () and found the OCaml sources of my js_of_ocaml compiled program available right here for stepping through, the abstraction was quickly leaking.

For example the representation of OCaml variables makes little sense unless you know a bit about the way js_of_ocaml represents ocaml values; see @vouillon’s paper.

I personally don’t mind that much since my debugging tool of choice remains printf which works perfectly in js_of_ocaml, but then I wouldn’t mind higher level luxury.

2 Likes