The Edge, Wit, and Courage of ReasonML
Ian Wilson
Posted on March 22, 2019
Photo by Donald Chodeva on Unsplash
ReasonML has stolen my attention recently.
If I'm not at work or running about, I am probably digging into some ReasonML/OCaml repositories like an addict in the tenderloin.
Perhaps I like it for the unparalleled incremental build speed.
Perhaps I like it because its not too different from JavaScript yet it nets me a plethora of built-in advantages like type safety.
Perhaps I like it because of my affinity for math in school, and the functional paradigm gives me a warm fuzzy feeling inside.
There are a few reasons, no pun intended, for my love of Reason. Just like a functional program wields function composition with immutable state to achieve its goal, Reason's composition of the best of OCaml and JavaScript make it a strong candidate for the future of programming.
The Edge of OCaml
For starters, Reason is basically a syntax upgrade to OCaml. Let me start by describing how that helps us.
Speed
OCaml is a pretty damn fast language. It's incremental build times dwarf that of almost any other language. Since Reason is just a toolchain on top of OCaml, it maintains the many of the same characteristics of OCaml.
For instance take a look at this table from the docs of fastpack, an alternative to Webpack or Parcel for bundling JavaScript applications.
~1100 modules / 5.3Mb / MB Pro 2017
Fastpack | Webpack | Parcel | |
---|---|---|---|
initial build | 0.811s | 3.86s | 11.07s |
persistent cache | 0.208s | N/A | 1.39s |
watch mode | 0.088s | 0.226s | 0.291s |
Fastpack is still rather new but these numbers are promising -- and the result implies we can made super-quick devtools for a vanilla JavaScript workflow.
Static Typing
Another thing Reason inherits from OCaml is a power, nearly impenetrable type system. The compiler does a solid job of making sure you exhaust all of your options in branching logic.
It also does away with silly errors like "undefined is not a function" or "cannot read property 'x'". The same could be said about adopting TypeScript or Flow, but they do not force you to cover such cases.
...with Great Inference
It's powerful type inference cleans up a lot of the verbosity associated with annotating functions and variables with types. The following function adds two integers. Not two floats or two strings, just two integers.
/* the type system knows that the arguments and the return value are ints here */
let add = (a, b) => a + b;
If that example is too basic, then try this one. Note how I am not annotating the types in the function signatures:
type animal =
| Dog
| Cat
| Octopus;
let animalToString = animal =>
switch(animal) {
| Dog => "dog"
| Cat => "cat"
| Octopus => "octopus"
};
let getWelcomeMessage = (name, visits, animal) => {
"Hello " ++
name ++
", you've visited this website " ++
string_of_int(visits) ++
" times and your favorite animal is the " ++
animalToString(animal);
};
The ReasonML/OCaml type system is capable of inferring based on our usage what the return types are, as well as each of the arguments. How neat is that?
Industry Proven
OCaml is a proven language of academics and industries where safety and mission critical code is paramount. This was one of the reason's it was adopted as the base for ReasonML.
At Facebook, OCaml is used to build some of their important everyday tools, like Flow and Hack. But they're also working on Reason, which in my opinion, is neat because Reason brings more joy than flow.
The Wit of JavaScript
With Reason comes a lighter, more JavaScript-like syntax. The first example I showed above would work just as well in regular JavaScript. One of the nice parts about writing vanilla JavaScript is that you can write pretty simply looking code that just works.
Add in TypeScript or Flow and you have less simple code with perhaps some awkward function signatures. Reason leverages OCaml's type inference to reduce the density of code in our programs considerably.
The result is a simple footprint like we'd read in a JavaScript program with all of the power of an OCaml script.
Syntax
Reason syntax is far easier to read and digest than that of OCaml, and most other functional languages for that matter. Let's take a look at this example from Cohttp, a library like Express or Axios for creating and handling HTTP requests.
This is a simple GET request with that library:
open Lwt
open Cohttp
open Cohttp_lwt_unix
let body =
Client.get (Uri.of_string "https://ianwilson.io/") >>= fun (resp, body) ->
let code = resp |> Response.status |> Code.code_of_status in
Printf.printf "Response code: %d\n" code;
Printf.printf "Headers: %s\n" (resp |> Response.headers |> Header.to_string);
body |> Cohttp_lwt.Body.to_string >|= fun body ->
Printf.printf "Body of length: %d\n" (String.length body);
body
let () =
let body = Lwt_main.run body in
print_endline ("Received body \n" ^ body)
Now if your background doesn't contain OCaml you may just want to look away. There are some funky operators working in that example. For instance:
- the first let binding is... a closure?
- what the heck is
>>=
and>|=
? - why does it use "->" instead of "=>"?
- whats up with the selective use of parenthesis?
- why is there so much piping?
- whats up with the
fun
keyword? - what does the keyword
in
mean here? - what issue does OCaml have with curly braces?
Some of these questions are just syntactic funsies we learn to deal with in OCaml. The piping is common to many other functional programming languages, and it makes for a cleaner data pipeline in some cases.
What might this same example look like in Reason?
open Lwt;
open Cohttp;
open Cohttp_lwt_unix;
let body =
Client.get(Uri.of_string("https://ianwilson.io/")) >>= (resp, body) => {
let code = resp |> Response.status |> Code.code_of_status;
let headers = resp |> Response.headers |> Header.to_string;
Printf.printf("Response code: %d\n", code);
Printf.printf("Headers: %s\n", headers);
body |> Cohttp_lwt.Body.to_string >|= (body) => {
Printf.printf("Body of length: %d\n", (String.length(body));
body;
}
}
let () = {
let body = Lwt_main.run(body);
print_endline("Received body \n" ++ body);
}
I think this code is a little easier to read. Those funky operators are still there but I'll note that they exist to help deal with OCaml promises, sometimes referred to as "lightweight threads".
Reason code takes the good parts of JavaScript style and OCaml style and finds a happy middle ground where people can be stylish and functional.
Easy Build System
Reason massively more accessible to newcomers than OCaml in that you probably wont spend days trying to install your dependencies properly. When using BuckleScript its often just another npm install {package}, then adding the name of that package to a bsconfig file.
The workflow for compiling to native is still a work in progress but Esy is working towards making it smooth.
If you need training wheels when writing Reason, do not fear. You can use special directives to write JavaScript directly in your Reason code. Its not cheating, the language was designed this way to make migrating codebases easier.
Reason's interoperability with JavaScript makes it a little more accessible until the developer is comfortable with removing the escape hatches. This is similar to using any
in TypeScript.
The Courage of Reason
Over the past few weeks, I've been exploring writing Native Reason code. I think some of the projects that are being developed show great courage in showing off the potential of ReasonML.
While I certainly appreciate it's benefits on the frontend when transpiled to JavaScript, it would be super neat if I could write it on the server too.
I want to communicate with databases, make blazing quick command line tools, and write super efficient servers, perhaps serving GraphQL.
And I want to do it with the same language that I'm writing in for my web applications.
Currently, we can do this with JavaScript, but I think we can do better.
By further leveraging the OCaml ecosystem we can actually write incredibly robust servers and developer tools.
The Esy package manager allows us to install and build packages from npm or opam in a convenient package.json fashion without having to manually hack system dependencies.
As Esy further develops, Reason on the backend will become increasingly more accessible to developers looking to write stupidly fast native code.
If you're interested in what's already been made with Esy, check out the following projects:
- fnm, an alternative to nvm for managing your NodeJS version. It's much faster than nvm.
- fastpack, a ridiculously fast JavaScript bundler
- revery, a desktop UI framework like electron, except without downloading a whole browser onto your users' computer.
And there are more out there in the Forests of Open Source.
This certainly won't be my last post on this topic so stay tuned for more Reason evangelism on the horizon.
If you want to how ReasonML works seamlessly with GraphQL, check out this article I wrote on ReasonML with GraphQL, the Future of Type-Safe Web Applications.
If you'd like to keep up with future posts, sign up for my newsletter here!
Posted on March 22, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.