Hi, the main author of BuckleScript here, below is a simple comparison with TypeScript (it may be biased):
- 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 TypeScript
I work on TypeScript at Google, so just one point about scaling. Ultimately the only way to make things scale to large codebases is to make separate compilation work. That's what we do: separately compile separate pieces of the codebase. Even if A depends on B, we only recompile A if the API shape of B changes. Our incremental compilations while editing code are generally in <1s range, and our codebase is much larger than VSCode.
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.)
I am a newcomer (started around 6 months ago), and it was not a smooth ride, but that was simply because of a lack of documentation.
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).
I'm more curious to see how this compares to PureScript (http://www.purescript.org/) rather than TypeScript. That seems more like a direct competitor in terms of bringing Haskell style algebraic data types into JS vs OCaml types / syntax in JS.
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.
> Compiler slow (not scalable for FB/Google scale)
I 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?
The relevent point IMO is the part about "10% perf improvement is a nice feature, a 10x one is invaluable".
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.
Yes, but how often is it that you have to compile all of the TypeScript at one of those companies all at once? In practice, your build system should be able to shard compilation of all of the TS at something of that scale over many, many machines.
Alltogether? Maybe. But for a single library or webapp it will be hardly that much. I doubt that Facebook JS frontend code is much more than VS Code.
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++.
Developed, yes, but in general, your build system bundles it all up into one javascript file which is sent to the browser. Even if you don't bundle the javascript, all the code has to be sent to the browser anyways, just over multiple HTTP requests instead of one.
Except for small or basic apps, your build system is not suppose to build to only one file. It's suppose to build to a main file and separated module files. The module loader will pull in modules only when needed.
Right, but there can be many bundles for complex sites like Facebook. They can have a bundle for Messenger.com since it's a SPA, another for the FB home page, others for object pages, search pages, etc. etc....
Nice work! A few questions: can you speak to why one would choose BuckleScript over js_of_ocaml? Also, does BuckleScript support all of OCaml? Could you compile core_kernel using BuckleScript?
You'd use BuckleScript to stay inside the npm ecosystem, and JSOO to stay in the opam ecosystem. Also BS if there's any chance that your output JS could be consumed by apps written in plain JS, or if you might one day want to move off of OCaml and back to plain JS.
Re: core_kernel, I personally don't know, but it should be possible to the extent that it doesn't call out to C.
There is a specific niche that BuckleScript and Reason can fill, and that is the sort of front-end projects that use React+Redux+ImmutableJS+TypeScript/Flow.
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.
TypeScript is far more powerful than you think. Between const, readonly properties, Readonly<T>, ReadonlyArray<T>, readonly indexers, and type safe object spread {...} via keyof T, TypeScript provides the tools to write immutable code, fully checked by the compiler, without paying runtime costs.
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.
I use TS myself. It helped make front end development OK for me again. But man, one really misses some things.
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.
* '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).
> * '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.
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).
1. I feel like I shouldn't feel ashamed when saying this, but here: mutation, imperative code & side-effect are kinda nice, and very much needed for a straightforward interop with e.g. existing JS/Obj-C/Java (hint hint) code. For example, notice how BS externals are erased & inlined. Most of our ffi doesn't have a conversion cost. An excellent ffi can be a deal maker, considering that if you're serious about migrating your codebase to a new language, you'd be using more ffi than idiomatic code for a while. Early on, I've had folks accidentally modify the output JS artifacts on messenger.com because they thought I wrote the JS code myself; they were generated by BS.
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)
> I feel like I shouldn't feel ashamed when saying this, but here: mutation, imperative code & side-effect are kinda nice, and very much needed for a straightforward interop with e.g. existing JS/Obj-C/Java (hint hint) code.
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!
Yeah. Some existing algorithms are more easily expressed with mutability, and for FFI it's very needed; I think some community-wide messaging regarding this is good enough. Constraining them at the type level might make adoption a bit harder.
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.
When I tried Typescript briefly, it seemed like the compiler was surprisingly slow. Not super slow, just nothing at all like writing raw JS directly, or Python or anything like that.
This blog post emphasizes that BuckleScript compiles very quickly. Sounds very appealing.
> 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.
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.
IMHO TypeScript/Flow missing pieces are 1) runtime side of type system (match) and 2) expressions instead of statements. Those two things complete the type system reaping rewards from annotating code with types. Both TS and Flow have reservations on adding those constructs to the language as it requires runtime support (match) and would be seen as too far departure from js (changing statement -> expression semantics).
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.
A similar stack is Fable and F# with the Fable-Elmish library. It also works well with react/redux tooling (debugging in the browser supported), and has great tooling support in Visual Studio Code.
Clojurescript is nice a option too, you get a language with better defaults and you don't have to pull in immutablejs/lodash or you favourite immutable and util library and deal between all the inconsistency between them. All the ES6/7/8 stuff is there too plus other niceties.
> You don't have to worry about JS scoping rules or how `this` works, or how ES6 classes are ultimately prototype-based
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 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.
Looking forward to giving this a tryout. We have a server-side Node.js project where performance is terrible, and memory leaks happen that are almost impossible to track down; I need to rewrite it, and I was thinking Go, but this project also has a bunch of code that is also used by JS clients; so something JS-compatible would be better.
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?
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.
Are there examples anywhere showing if this is productive to use with a UI framework (e.g. Angular, Vue, React)? What are the thoughts of people that have personally tried it?
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.
How could I incrementally migrate a TypeScript React project to a BuckleScript (maybe using Reason bindings) React project using webpack? Could I easily add the bs-loader and just import BuckleScript components into my TypeScript components? Can BuckleScript share types with TypeScript or would that require an external TypeScript type definition file?
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.
JavaScript is becoming a must-have compilation target. I hope that we can cut out the middleman soon via WebAssembly.
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.
WebAssembly is quite a small change from asm.js - it's mainly a different transfer format and a parsing speedup. It's targeted towards non-GC languages like C/C++ and Rust - for managed languages, Javascript is the only sane compile target.
Scripts for GC languages wouldn't be compiled directly for WebAssembly -- their runtimes would. That's why I suggest browser vendors bless a few specific alternative runtimes and distribute them with the browser. For example, PyPy would be compiled for WebAssembly, and then Python could be executed directly as a client-side web language. Same with Dart and other languages that depend on a VM to do the heavy lifting.
Yeah, though I haven't worked on that directly, I'm sure it's not trivial, but I think there is sufficient demand. Check out http://pypyjs.org , which uses emscripten to compile to asm.js and then bolts on a custom JIT that emits asm.js, for one example of a project that is already trying something like this.
I guess if wasm some day gets GC support and runtime code generation, there might be something to this idea - for a language like OCaml that is designed for AOT compilation.
FWIW, if you're looking for compilation speed I suggest HaXe. Its syntax is very familiar to TS / AS3 / Flow developers, and the compiler is very fast.
He honestly lost me when he said he couldn't understand how this works in JavaScript.
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.
You should have just seen how the compiler works. There are examples on the side.
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.
It is extremely fast. I think you'll be very pleasantly surprised by the runtime speed of OCaml if you're coming from interpreted languages, and by the build/tooling speed if you're coming from compiled languages. It really does hit a nice little sweet spot. :)
Yes, literally when I type in VS, there's a noticeable delay before the letters appear on the screen. I think it's working hard to figure out code completion. Maybe this is just a problem in the Mac version. Anyway, it's very frustrating, as computers are generally thought to be pretty fast these days!
BuckleScript is an OCaml to JS compiler. So you can convert your code into JS and run it from Node. Or, you can simply compile it into native OCaml binary and run it just like you run a C or GoLang binary.
The choice would depend on whether you use libraries from the npm ecosystem, or from the OCaml ecosystem.
There is Ocsigen (https://en.wikipedia.org/wiki/Ocsigen) which lets you write OCaml on the front-end and back-end, and is a fully-featured self-contained server.
Yes, Cohttp is from the folks who developed the Mirage unikernel: https://github.com/mirage/ocaml-cohttp . They make lightweight stuff that runs blazing fast.
I've been rather worried about the state of parallelism or concurrency in Ocaml, two conecpts which I have little understanding of, but the rumours of the insufficiency in support in the Ocaml compiler/interpreter have offput me from using Ocaml.
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?
It's not a problem in practice. OCaml has great concurrency support, which is what you need for applications that do IO, like servers. For CPU-bound applications like number-crunching, you take advantage of existing bindings to C or Fortran libraries. For a thorough discussion, see https://discuss.ocaml.org/t/7-years-after-is-ocaml-suitable-...
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.
- Pros of TypeScript
- Cons of TypeScript - Pros of BuckleScript - Cons of BuckleScript