BuckleScript: An OCaml to JavaScript compiler
yawar.blogspot.com
yawar.blogspot.com
- Pros of TypeScript
- Designed for JS, easy inter-operate with JS
- A much larger community
- Nice integration with VSCode
- Cons of TypeScript - Compiler slow (not scalable for FB/Google scale)
- Verbose, very limited type inference
- Start up time slow (very hard to build traditional build tools)
- Types are only used for tooling - soundness is not the design goal, not very reliable
- Only JS backend, no code optimizations
- Pros of BuckleScript - No legacy of JS (clean slate) while great story in inter-operation
- Compiles much faster, scales much better
10% perf is a nice enhancement, 10x faster is an invaluable feature
- An arguably much better language
- Sound type system, global type inference
- Types are used in code optimization, optimizing compiler
- Javascript backend + Native backend for X86, ARM
- Cons of BuckleScript - Smaller community
- Learning curve is higher compared with TypeScriptI don't have any dog in this fight(I write Java/Native mostly) however I'd don't quite follow what you mean by FB/Google scale. I would consider VSCode a pretty complex piece of software and my understand is it written in TS.
Have any concrete numbers you can share that makes it not meet your bar?
I personally have only worked on up to medium sized projects (maybe 40000 LoC) in Typescript. I found compile times not to be a problem there, and I even though they were really on the good side of the spectrum. Didn't noticed any difference compared to similar sized C# or Java projects. All of those 3 have pretty neglible compile times compared against FB and Googles other main programming language: C++.
Essentially you have the svn branches vs git branch scenario here: svn branches were fast enough and people worked around the perf issues just fine with various strategies, bit git branches come in and change how branches are used altogether.
So sure, you can just break down the project in a lot of libs or sub projects (that's what we do here: our app is broken down in hundreds of smaller ones so we never build the whole thing), but what if you didn't have to?
Extreme case, in a magical land of fairies and unicorns: what if it was possible to git clone 100 million lines of code instantly, load them up in your editor (all of it) instantly, do all refactors/searches on the entire code instantly, live (without caches), and at every single keystroke rebuild everything, every time, with zero latency. How would that change your workflow and some architectural decisions?
This is not a real question, but just something to think about, when we're talking about a compiler that could be 10x (or more) faster.
Although the last time I used PureScript it was still very much in an immature alpha stage with frequent breaking changes with each new release. I'm excited to see what it grows into.
Now I'll have to give BuckleScript a shot.
PS: thanks for your hard work on this project :)
It's not clear to me what the volume of bugs, edge-cases, and likely future-backwards-incompatibilities are like.
I used to be a big fan/user of OCaml and though I haven't looked at BuckleScript in particular it sounds like a cool project. I think one con you overlook is that OCaml syntax/ecosystem is pretty hard on newcomers, though maybe Reason helps there. (And also my OCaml experience is years out of date so I'm sure it has improved since then.)
It has changed a lot since then - more people are writing about it, the Reason homepage itself has got a facelift with a bunch of new content, there are great error messages, there is always someone in the Discord channel to help you out, there are more and more bindings coming out every month, `ReasonReact` is seeing active improvement, and there is excellent IDE and tooling support.
I can see a clear increase in momentum as more people join, and I think it'll only get better as the new syntax is released (which is very similar to Javascript than the typical space-delimited auto-curried FP syntax).
Re: core_kernel, I personally don't know, but it should be possible to the extent that it doesn't call out to C.
Instead of bolting on typing into Javascript (Flow and TS both do a good job of it btw), you could simply use a language that was designed with it. There is a huge difference in native adoption of such a core language feature vs using a transpiler that ultimately leak Javascript semantics.
If you're using immutable values through ImmutableJS-like libraries, or by just being careful to not mutate stuff - then you should try working in a language that has immutability baked in. Having confidence that you've been careful to not mutate anything in your code vs using a language that simply disallows it in the normal course of programming brings a tangible difference.
You can call BuckleScript/Reason as just a better version of Javascript with all the good things baked in. But it definitely is much more - you get to use Variants (Sum/Union types in other parlance), and more importantly, writing value-based immutable code (instead of reference based mutation) lets you rely on equational reasoning to understand your code. Your functions are pure and can be seen as though they are equations, and your whole program is a composition of these equations.
A good thing about BuckleScript/Reason vs transpiled languages like CoffeeScript is that we're fully free from Javascript's semantics. You are solidly inside OCaml, and Javascript simply doesn't leak through. You don't have to worry about JS scoping rules or how `this` works, or how ES6 classes are ultimately prototype-based. Except for a well-defined foreign-function interface that lets you use all of npm libraries or your existing JS code, once you are in the typed immutable world of OCaml, then that's all you need to think about.
All of this applies not just to BuckleScript, but any Typed FP that runs on the browser - Elm, PureScript, and I believe Scala.js as well.
But one big advantage (or disadvantage, depending upon how you look at it) of OCaml/Reason/BuckleScript is that it allows mutation if you need it. You can start out writing purely imperative code just as you would in Javascript, and slowly adopt immutable programming as you get comfortable with it. You can also rely on mutation when you need to optimize specific parts of your code.
I don't worry about these things. I feel like I'm writing a lot less JavaScript these days, mostly through the power of React, JSX, and ES6. I only really use Typescript to type DTOs. I just don't have enough problems to scrap everything for a completely new language.
I was not recommending that at all. I tried out Typed FP only after a project with a lot of complex data transformations (which is a sweet spot for Hindley Milner based types) and couldn't hold it all in my head. In the React world, the equivalent would be a deeply nested component hierarchy, passing around a lot of props, and having a large state-tree and correspondingly complex reducer.
You can definitely use the typical Redux+TS/Flow+Immutable stack there without any issues, but I think there is a much better developer ergonomics on offer here.
It also has tagged unions, product, and sum types, nullability, and extensive capabilities for typing 'this'. So I'm left to ask what exactly is the unique advantage of writing JS in OCaml? Because it's not immutability. Remember too, that OCaml has plenty of warts.
* 'readonly' support, while nice to have, is mostly broken. It's just way too easy to coerce a readonly value into a non-readonly one, especially with nested objects.
* There are no proper sum types. You can emulate tagged unions with a type field as a tag and match on that and get good compiler support for the fields, but in no way does it feel like a proper, language-supported sum type.
* Javascript semantics leak through to TS all the time. Which is kind of the point, TS is supposed to be a static typing layer on top of JS.
This is an advantage too: you stay close to the actual runtime and it's easy to interact with non-typescript code.
But a lot of stuff still feels quite messy, like a bolted on abstraction (which it is).
Do you have any example of this? To be honest, as much as I'd love immutability support built into the language, the number of bugs I get from accidentally mutating data is so low it's not something I would make a lot of effort to get. There's always Object.freeze as well which will throw an error if you try to violate immutability.
> * Javascript semantics leak through to TS all the time. Which is kind of the point, TS is supposed to be a static typing layer on top of JS.
Does this not happen in BuckleScript/Reason as well at the layer where you interface with JavaScript libraries?
> But a lot of stuff still feels quite messy, like a bolted on abstraction (which it is).
Yeah...it's practical though and has an easy plus incremental migration path. Switching to OCaml would be much bumpier.
If you have some time, please give that intro page a read. Hopefully that intro page explains what we're achieving. If you'd like more nuanced opinions, we're always in Discord (discord.gg/reasonml).
Hope to see you around =)
Does the lack of tail recursion optimisation in browsers not present an issue? Seems it's not well supported yet: https://kangax.github.io/compat-table/es6/
Also, why did you decide on OCaml and not say Haskell?
I'm not likely to migrate existing projects soon but I'll certainly give BuckleScript or Reason a try for something self contained in the near future.
2. Also, speaking of native compilation: we're not reference counting but _some_ predictability when working with UI code is needed. The GC has to be fast and collect things within 16ms (and now, 8ms since iPad Pro is often 120fps). The ocaml gc achieves that.
Those aren't my findings: those are the findings of the creator of React, who also created Reason.
3. Finally, and to corroborate with what we wrote on that page: if you go ask your "normal JS colleagues", you'll realize most folks don't _actually_ know the semantics of JS. They like JS because it _looks_ familiar. Is TypeScript "just JavaScript"? Does it matter if JS folks can pick it up and be equally productive quickly? OCaml/BS semantics map over to JS well enough that we can just change the syntax into something familiar, and you'd likey end up writing the code you'd usually write in JS, albeit backed by a type system that needs to make no excuse in terms of quality.
Hope that helps? Tell me if that makes sense.
(Tail recursion has been a problem for a single person so far afaik; you do have imperative loops)
Similar feelings...OCaml has mutability built in but you don't need it often. You can isolate its use to small areas of your code to keep it under control. If you went the Haskell route getting JS developers to adopt would be even harder.
> Also, speaking of native compilation: we're not reference counting but _some_ predictability when working with UI code is needed.
So the GC is well behaved with Reason scripts? I'm curious if you could use it for games.
Thanks for the insights. I haven't heard of Reason much to be honest which is a shame. I used OCaml as my main language for several years and going back to Java, Python and JavaScript was pretty painful!
We're just a syntax for OCaml; we don't change how it runs. Same GC. Reason's mMuch less sophisticated than you think, but also much fewer unknowns. https://reasonml.github.io/try/
The goal of Reason (and of BuckleScript) is so that people can convince their coworkers that OCaml isn't an esoteric unmaintainable language. BuckleScript is JavaScript: The Good Parts: The Good Parts.
TypeScript doesn't support deep readonly, which would be really helpful for Redux, where you're required to copy anything you change, but can easily forget.
This blog post emphasizes that BuckleScript compiles very quickly. Sounds very appealing.
I have similar feelings to be honest. Working in TypeScript, I know I can easily Google for JavaScript snippets, libraries and issues when required but if I'm working in OCaml it would feel too isolating. In TypeScript, I'm already using immutability, non-null/undefined types, no implicit any types, modules, map/reduce/filter etc. I'm not sure OCaml has enough advantages to overcome the downsides of working in a niche framework right now. I do miss OCaml's terse syntax though.
The unwritten motto of TS/Flow is "stripe type annotations and you've got vanilla js". Adding support for match and expressions would mean adding runtime dependency and transformations which would violate this 1-1 symmetry.
This is a shame because type annotations are the hard/boring/sacrifice part, runtime matching is easy/fun/benefit out of it and expressions are just... I don't have words... - why would you think of making any of those statements? It's just pure limitation, adding verbosity, without any reason.
* OCaml's type system is sound, Typescript’s isn’t.
* OCaml is an expression-based functional language, with first class currying and partial application, TS is an OO language.
* OCaml has pattern matching and exhaustive checks, TS doesn't.
* OCaml leverages true immutable structures, TS doesn’t.
* OCaml has a super fast compiler, TS doesn't.
OCaml syntax (and, IMO, any ML-like) is far better than C-like syntax, IMO.
I recently picked it up again and struggled to remember how to properly do pattern matching and exhaustive checks, until I realized that there were more hoops to jump through than at a circus convention. A friend ultimately pointed me to https://pattern-matching-with-typescript.alabor.me/ but that made me weep.
Then I remembered how I hadn't been able to figure out how to use object spread to perform variable assignments of all members from an object returned from a function, with compiler verification that no object members had been forgotten. That may be possible now, I'm not sure. I don't think it was possible last I battled with it.
Don't get me started on the ergonomics of immutables. Again, this may have improved, and no one would be happier about that than I.
But all in all I feel that TS is not what I'd like to use for front end development. However, it's where the community and development effort is at, and I feel like I could never convince my colleagues to go with BS/PS/Reason/anything else. I haven't even been able to push them from JS to TS yet.
If someone wants to show me the error of my ways, I'd love it, since I'll probably be using TS for some time to come.
I'd be happy to be writing any Typed Functional language - the differences between them are inconsequential compared to anything outside the paradigm.
What's BS's story when it comes to interfacing with async code? In particular, all my Node.js code these days is promise-based (I promisify all callback-type functions, except for event-based ones that I try to wrap) using async/await. Do you still have to use promise calls in BS? Or are compatibility operators offered for chaining async calls more elegantly?
What about async in general? OCaml has LWT -- can you use that with BS and still run on Node.js or in the browser?
We also have Lwt compiled with BuckleScript: https://github.com/mzp/bs-lwt
But, bs-lwt right now is not compatible with promises--it is a straight port of the OCaml implementation. I'm told that someone is working on an Lwt backed by JS promises for BuckleScript.
OCaml is one of my favourite languages but it feels like it would be going against the grain just too much right now compared to using something like TypeScript.
A long discussion about using it[2]
And a react example[3]
[1]https://github.com/OvermindDL1/bucklescript-tea
Facebook has started rewriting several things internally with Reason, including Messenger (which uses React, I believe).
I personally work on it too and have been using it on messenger.com
OCaml -> AST -> JavaScript
It even solves known expressions at compile time.
It looks very interesting and I am really missing some of the features when working with TypeScript. It could be useful if I could slowly transform the codebase from TypeScript to BuckleScript.
In your case you need to make sure you're binding against the JavaScript output, not the TypeScript input. Drop by the #react channel in discord.gg/reasonml to chat.
In particular, I would like to see browser vendors distribute a small number of wasm-compiled VMs that function as first-class JS alternatives. This would provide some sense of stability and blessing, and prevent a delay in page load time caused by the download of an alternate client implementation.
I started to seriously undertake a Dart-based project in the last couple of weeks but eventually decided that the ecosystem is too immature to make it worthwhile. I'm going to try Scala.js for a while and if that doesn't pan out I'll probably be forced into TypeScript. I had previously also tried Go/GopherJs.
- Save significant amounts of time during each edit-recompile cycle (this really adds up)
- Get better type inference and write more succinct code
- Deploy tiny artifacts thanks to BuckleScript's aggressive dead code elimination, ES6-style module output, and rollup.js's fantastic tree-shaking
- Have fun coding
It is not a walk in the park to compile runtime into WebAssembly (unless you are just compiling an interpreter).
Performant runtimes usually include machine specific backends and/or hand written assembly code. You'll need to port that all to target WASM too.
....because it's written in OCaml ;-)
Reason => OCaml => Js: https://reasonml.github.io/try/
Also, what is meant by Google scale? Do real people really go around choosing a language for a project thinking "gee, if I ever get to the size of Google I'll sure be glad I choose BuckleScript!"
Also, what is meant by an optimizing compiler? The optimizations are done by V8. Any possible pure language optimization you could be doing likely isn't worth it at all.
It's also not correct to say there's no JS legacy. There will be a legacy the first time you need to look at any of the generated JavaScript code.
Next, who wants to build a code base using a language that's hard to learn and come up to speed in? If the entire point is net productivity, you better be able to offset all that upfront costs by demonstrative gains elsewhere. Of course, no one will ever measure stuff like it and it will be very subjective and vary per person.
For example: an {a,b} struct is compiled down to a [a,b] array in JS. (by the compiler).
This is an optimization. V8 can only optimize the code that it is given (and sometimes, deoptimize). The better your code is, the more easier V8 can optimize it.
// Unoptimised
let x = 10;
console.log(x);
// Optimised
console.log(10);
I completely agree with all the other points you made tho.The choice would depend on whether you use libraries from the npm ecosystem, or from the OCaml ecosystem.
There is also Opium (https://github.com/rgrinberg/opium) which is a Sinatra-like light-weight web server.
Neither has the kind of vast community support like Rails or Django enjoys, and I haven't used them in anger either.
Would someone be so kind as to shed some light on this issue for me? Does it affect, say, a web server implemented with OCaml? What about a program in which some background process is used to prepare something simultaneously to the main process, like loading assets for a game?
Also remember that with BuckleScript, you're deploying to JavaScript, which is single-threaded by definition. OCaml's runtime model (modular, single-threaded, strictly-evaluated) maps beautifully to JavaScript.