JS_of_OCaml vs Bucklescript

Hello, dear community!

Currently we have 2 options to write frontend-related code in OCaml: js_of_ocaml and bucklescript. I have no experience with this two libraries and I want to pick one. Has anybody tried both of them or made some analysis with comparison? Could you please share your thoughts and experience about both libraries if so. I’m interested in: performance of generation, source-mapping, performance, quality, safety and readability of generated JS, simplicity of use, pros and cons of both solutions, etc. Thank you very much for your responses. OCaml community is the best!

5 Likes

I’d like to understand why a readable output is considered so important by many developers using those tools. It can be convenient the first 20 minutes, to check that the code is doing what one would expect. But after that I don’t see the point. Even for bucklescript which pretends to generate a very readable output, it quickly becomes a mess impossible to understand and source map get more valuable in my opinion.

So is it just for trendy reasons? Or to help beginners? Or there is a something I’m missing?

3 Likes

You may need to read a lot of generated JS code to optimize its performance through optimizations in your OCaml code.

One reason for readability is for when you are writing bindings to JS libs. You often need to make sure that the generated code really do what you intended to, since the types will just believe anything you write at that point and won’t really help.

1 Like

I don’t really buy this argument js_of_ocaml has a precise FFI that maps onto the JavaScript language constructs. So as long as you know what you need to bind to (function, method, property, etc.) there’s no need to check the generated code.

3 Likes

When doing interop with JS code there is a big chance you will need to debug JS code at some point. Ideally the “assembly” would be abstracted completely away (never see any JS), but that’s not an option for the foreseeable future.

1 Like

You will likely have to debug hand-written JavaScript code…

It is however unlikely you will have to debug FFI or OCaml compiled to JavaScript code by looking at the generated code; in the same way the working programmer rarely has a look at the assembly generated by the OCaml compilers — except if obsessed by speed.

2 Likes

We’ve made extensive use of js_of_ocaml for internal apps at Jane Street. I can’t give a detailed comparison with Bucklescript, but I can tell you what I know of the tradeoffs.

  • First, js_of_ocaml runs pretty fast, but I’ve heard tell that Bucklescript is faster. js_of_ocaml now supports separate compilation of Javascript, so subsequent recompilations are quite zippy, in my experience. That said, the initial compilation takes material time. Dune does separate compilation for js_of_ocaml by default, and does a single, more compact javascript executable when run in production mode. Anyway, we haven’t found performance of the compiler to be an obstacle with js_of_ocaml.

  • js_of_ocaml is highly compatible with OCaml’s semantics. Advanced libraries like Async and Incremental that make fairly aggressive use of OCaml’s memory model work under js_of_ocaml without modification, which is great. I believe you have to be a bit more careful when compiling with Bucklescript. (See incr_dom for an interesting application of Incremental to the browser.)

  • js_of_ocaml is highly compatible in a another way: it is essentially always fully up to date with the latest OCaml. That’s because it’s easier to maintain, by virtue of operating only on OCaml bytecode. Bucklescript is a more fullsome set of patches to the compiler, and so it typically lags a few versions behind. That alone is for us a sufficiently compelling reason to stick to js_of_ocaml.

  • Bucklescript seems to have a more active web-dev community, associated with the Reason community. I think this is mostly because Bucklescript generates easy to read javascript output. I don’t care much about readable JavaScript output (especially in a world with sourcemaps), but the community surely has value. For example, the OCaml React bindings are currently Bucklescript-only (though @jordwalke has suggested that porting to js_of_ocaml would be totally doable.)

Anyway, good luck in picking! I think people have had good experiences with both, so you can’t go too far wrong.

y

19 Likes

Oh, and for people who are concerned about bindings, this project from LexiFi looks pretty cool. I think we haven’t started using it yet, but some folk internally are currently kicking the tires. It looks pretty cool to me.

10 Likes

Relatedly, though it’s young, there’s reasonably-typed, which is BuckleScript-specific instead of jsoo-specific; and which works in the ‘opposite direction,’ so to speak. It’s not very complete as of yet, though; they have a test-suite which runs across a huge body of existing JavaScript typing definitions, and only about half of them are passing.

I’m relatively new to the community (just logging into the forums for the first time right now, though I’ve been active on GitHub, and occasionally IRC, for the last year or so), but maybe I can shed some light on the “readable output” thing.

With traditional compilation targets, the overall product that the output of the compilation is fitting into is generally under the control of the author choosing the aforementioned tool doing the compiling: if you’re writing a C program, you’re maybe consuming one or two huge, monolithic libraries; and chances are, you’re contributing little-to-nothing back upstream to the OSS community. That is, if you decide to switch to, say, OCaml, for some portion of your code, you’ve nobody (at least in spirit) to answer to except yourself and your team of collaborators.

The backend (and slightly more recently, frontend) JavaScript communities, though, are super-tight, in the OSS sense: chances are extremely good that some, if not most, of what you’re writing is going to go straight back up on npm for somebody else to consume — and crucially, for somebody else to probably contribute back to. My experience over many, many years, is that 2010-onwards JavaScript, Node, and npm have some of the most prolifically active and incestuous OSS models I’ve ever seen. (… not to say that this is a good thing, or that it leads to good software … I mean, this one-function package has 10,000 dependants, 200 commits, and 84 pull-requests …) When JavaScripters write code, they expect many other JavaScript developers to read that code — many of them possibly new, hobbyist, or young.

Correspondingly, when things like CoffeeScript (which, by the way, pioneered this “readable compilation-target” thing as a whole) started to come along, the many, many people who didn’t write CoffeeScript would be rather unhappy when they’d come across a project they wanted to use, (and I repeat: as a JS dev, “to use” implicitly means “to contribute back to”) and it would be incomprehensible to them. Well, eventually, between the complaints thereof, and the preponderance of compile-to-JS-language projects out there that were seeing subpar or nonexistent contributions from the community that ran them, we eventually ended up with a collective mindset that the code you push to npm must be readable, plain, ES5. You could write CoffeeScript, TypeScript, hell, Clojure if you wanted, as long as the JavaScript was the canonical, hackable source of the projuct.


Anyway. History aside: a huge, huge selling-point of the ReasonML ~thing~ (specifically BuckleScript, but nobody following the hype seems to actually call it that — it’s all just ‘Reason’), is that the Hardcore Nerd who’s actually into this stuff, doesn’t have to convince the ~100 other developers on the OSS project that ‘hey, we need to stop what we’re doing, all of you need to learn a new language, and we need to rewrite the entire project in a functional language!’ It’s sold, bottom to top, as something that you can add to existing JavaScript codebases incrementally: start out with one file — just go rewrite that horrific spaghetti parser in Menhir without asking anybody, compile it down to JavaScript that’s supposedly indistinguishable from your collaborators’ hand-written code (especially after it, like all the other JS in the project, goes through the opinionated auto-linter the team mandates …). Hell, you can even git filter-branch the “weird”, “freaky”, neckbeard-code entirely out of the branch you submit the pull-request against. :P Everything in the ecosystem they’re building seems focused on that: bsb fares extremely weak when compared to Dune; but is strongly designed to cohabitate with multiple other build-systems existing in the same project.

Thanks to these same priorities, one of the very best things BuckleScript brings to the table, is exactly the opposite of what @Yaron_Minsky wrote above: BuckleScript is happy (sometimes too happy …) to abandon OCaml compatibility, ease of use from that direction, and intuitive-to-old-hat-OCamleers semantics, to map more cleanly to JavaScript semantics. That Reason file I described above? It’s expected to return a first-class JavaScript Promise, handle all cooperative concurrency internally using the JavaScript event-loop, use obviously-polymorphic JavaScript debugging/printing tooling instead of complicated-but-runtime-typerep-free ppx_show magics, cleanly map both records and hashmaps to JavaScript’s heavily-overloaded ‘Object’ instances … so on, so forth. Yes, this has a lot of obvious downsides, when viewed from the OCaml side, which has plenty of perfectly-good, immutable, performant data-structures and types. That’s not the point — the point is literally writing JavaScript-semantic code, that naturally and directly manipulates these JavaScript concepts, in a sound hindley-milner inferrant environment that lends itself to rapid refactoring, y’know?

(All the above isn’t to say I, personally, subscribe to a lot of these viewpoints, by the way. I’m just neck-deep in the Reason hype-wave, and this where, why, and how I see other JavaScript devs looking into ML for the first time due to Reason, whereas js_of_ocaml garnered absolutely no interest whatsoever from the other side of the fence.)


tl;dr: JS devs were hurt, bad, by their last relationship with a compile-to-JS tool. They’re often extremely sensitive about a tool demanding holistic ownership of their projects, contributors, and mindspace.

tl;dr 2: it’s all about how it’s being marketed. to nobody’s surprise. :P

19 Likes

If the output of Menhir, translated by Bucklescript, is indistinguishable from your collaborators’ code, you need new collaborators :slight_smile:

5 Likes

what, you don’t head-compile all your algorithms down to a GBOM state-machine?!

anyway, yeah, like I said. Didn’t say it made sense all the time. It’s just the prevailing mindset I see. :stuck_out_tongue:

I heard an anecdote where a person managed to convince their boss switching to Reason/Bucklescript because Reason looks a lot like Javascript, and the output from Bucklescript looks readable. In other words, the “readable javascript” argument (however bogus it may be) can be used to convince Javascript developers and sneak in OCaml that way.

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