X Marks the Spot (Part 1): ReasonML

This is part one (of three) in a series about ReasonML and typed functional programming. Once posted, you can see the full series here.

Introduction

posts/2019-03-04-x-marks-the-spot-reasonml-and-reasonreact/wilsons-curve.jpg

I'd like to introduce Wilson's Curve1, which I have entirely fabricated based on nothing, but which is nonetheless very real. I created it as a reference tool to communicate a language's type system complexity against the benefit you get out of that system.

There are a series of languages across the bottom ranked roughly according to type system complexity. (Also, this is mostly a joke). The blue line represents an inverse relationship. That is, as the line decreases, more and more things can be ruled out at the language level. You can write down more detailed type information and more involved specifications. As you move down the blue line and the language becomes more finely grained, you'll also spend more time writing those specifications. Up at the top, which I've labeled "Good Luck, Buddy," you're on your own for making sure all the language "Lego pieces" fit together. Overall, you could view the blue line as a proxy for the number of syntactic and semantic gotchas that you might expect to be left lurking in your code—all things being equal.

Now, let's take a look at the red scale. At the bottom, you're untroubled by the learning curve. Sure, there are some WATs, but you're also not writing proofs. If you're familiar with curly-brace languages, you'll be in familiar territory at the left. At the upper end, your face tends to melt off (figuratively speaking, of course).

Naturally, there's a spot where these lines cross. In a language located at this spot on the curve, you can write down interesting properties of your program and squash some beefy bugs without starting the steepest ascent along the brain-melt curve.

There's a language inhabiting this region, it's marked with a box. It's beyond Java, yet it's not Haskell (bee tee dubs, I'm in no way taking a swipe at either of those).

Finally, let's yank off the mask (written in the solid box) and see that there's really a whole class of languages here. These—and many more like them—are in the ML language family; Elm, F#, OCaml, and SML. They're nestled on primo real estate in the language arena. And so here's my claim:

Programming languages near the "X" are a great fit for general application development of the kind that most developers do. -- Me

ML Language - ReasonML

There's a rather new entrant into the ML family, ReasonML. I'll spend the rest of the article discussing ReasonML, ReasonReact (ReasonML's React.js library), and why I think that ReasonML may be a good choice for you if you've doing modern single page app development.

ReasonML, or just Reason from now on, is a respin on OCaml. OCaml itself isn't a new language, having emerged in 1996. On the front end, there's a tool in the Reason toolbox, refmt, which takes Reason source code and compiles it to OCaml. Then the OCaml toolchain takes over and typechecks and otherwise works with the code in the way that OCaml normally would work. On the back end, Reason uses the BuckleScript compiler to produce JavaScript. The BuckleScript project aims to produce "clean, readable and performant JavaScript code."

The state of play is then to take an ES6-like syntax, convert it to OCaml, and then compile that OCaml to JavaScript. In between, there is all the type-checking, optimization, and compiler know-how of a mature functional programming language.

Lots of Tools

An interesting consequence of the Reason tooling is that it subsumes many of the ecological niches of its JavaScript counterparts.

There are a lot of tools in a modern JS app. A key feature of Reason is that it brings many of these tools into either the language itself or into tools with which it ships.

The full OCaml type system takes the place of gradual typing provided by projects like TypeScript or Flow. ReasonReact includes features that work much like redux. refmt, by parsing Reason into an AST on the way to OCaml code, is well placed to also pretty-print; this takes the place of Prettier. OCaml includes a system for syntactic extension (macros!), and this allows ReasonReact to do the work of a compiler like Babel.

Since Reason is ultimately just OCaml2, the compiler can produce bytecode and native code—besides just JavaScript.

Language Superpowers

Reason inherits the flexible type system of OCaml. This means that you rarely need to write down explicit type signatures, but all expressions have a definite type. This differs from tools like TypeScript or Flow because you won't accidentally end up with a bunch of any types floating around. Because, while Reason's types are inferred, they are also comprehensive in the sense that every expression is assigned a definite type. This means that you can rely on the fact that every expression from every piece of code you wrote, and all libraries other people wrote, is well-typed.

Reason also has records. Records are like beefed-up JavaScript objects. When you're using objects as a heterogeneous container of things to pass to functions, that's a great example of where a record fits well:

type user = {
  firstName: string,
  lastName: string,
  age: int
};

let fullName = usr => {
  usr.firstName ++ " " ++ usr.lastName;
};

Though we didn't mention user in the declaration of the fullName function, the compiler can check that we're only using fields of the user type!

Reason is really speedy! The compiler can perform fast type checking & compilation. The provided example ReasonReact "hello world" application takes a mere 0.8 seconds to compile!

Lightweight JavaScript

Reason and BuckleScript aim to emit efficient and minimal JavaScript. With the added information that the typechecker has, code can often be simplified.

Here we have some of the royal houses of the galaxy listed along with their homeworlds and annual spice production. Sorry (not sorry) my Dune fanboy got the best of me:

type planet = {
  name: string,
  house: string,
  spice: int,
};

let dune = {name: "Arrakis", house: "Atreides", spice: 100_000_000};

let geidiPrime = {name: "Giedi Prime", house: "Harkonnen", spice: 0};

let _ = Js.log(dune.name);
let _ =
  if (geidiPrime.spice > 0) {
    Js.log("uh-oh");
  } else {
    Js.log("[sad trombone]");
  };

planet is a record type consisting of three named fields along with their corresponding types: name has type string, house likewise, and spice is an int.

We then create two values (using let, the only binding form in Reason) having type planet. Next we console log the real name of dune using the Js interop module. There's then a quick check to see if Geidi Prime is producing any spice.

When compiled, we get the following JavaScript:

// Generated by BUCKLESCRIPT VERSION 4.0.0, PLEASE EDIT WITH CARE
"use strict";

console.log("Arrakis");

console.log("[sad trombone]");

var dune = /* record */ [
  /* name */ "Arrakis",
  /* house */ "Atreides",
  /* spice */ 100000000
];

var geidiPrime = /* record */ [
  /* name */ "Giedi Prime",
  /* house */ "Harkonnen",
  /* spice */ 0
];

exports.dune = dune;
exports.geidiPrime = geidiPrime;
/* Not a pure module */

There's some deep compiler smarts on display! The compiler does lots of constant folding, and dead code is eliminated. Note that the planet type is completely compiled away—it's been replaced with an array. We can't see it here, but record field access would simply be array indexing in JavaScript: dune.name would become dune[0].

In part 2 we'll go on and see how Reason & React go together like peanut butter and jelly.


  1. This post was originally a talk. You can find slides here

  2. Wadler's Law: In any language design, the total time spent discussing a feature in this list is proportional to two raised to the power of its position: 

    1. Semantics
    2. Syntax
    3. Lexical syntax
    4. Lexical syntax of comments

    But think of it the other way around: if you can effectively disarm any discussion of 2 through 4, then you've tricked everyone into talking about what matters, semantics.

    Said another way, if your language looks like JS, then people will evaluate it based on more meaningful aspects. You’ve operationalized Wadler’s Law! Cf. The Language Strangeness Budget by Steve Klabnik.

Chris Wilson, Technical Expert

Chris Wilson is a Software Crafter at 8th Light in Madison, WI. He's always on the lookout for how software can better solve people's problems.

Interested in 8th Light's services? Let's talk.

Contact Us