Cool Things Reason Formatter Does

Reason comes with a formatter that takes your badly written code and turns it into a well-indented piece of art:

Think of it as Prettier or Gofmt but for Reason.

Arguments about how to format code (and syntax in general) have plagued us since the dawn of programming; I’m not here to write about that. Instead, I’d like to show how refmt (“Reason format”) goes a step further and bakes in some of the wider-reaching semantic changes into the syntax itself, and why we do things this way.

JSX

JSX is a syntax that usually desugars to a function call, famously used in React for describing a UI tree. Reason has it too (React and Reason are both created by Jordan). The difference is:

  • We’ve built this JSX into the language-level.
  • It’s React-agnostic.

What does that have to do with syntax? Well, if you prettify your code by running refmt on the following:

MyComponent.createElement message::"hello" children::[] () [@JSX]

You’d get:

<MyComponent message="hello" />;

That’s a big syntax “formatting” refmt just did! So different that calling it formatting is almost a misnomer; but it’s indeed just a syntactic thing: those two lines are semantically equivalent. If you examine both abstract syntax trees, they’re identical. Get it?

For those of you who don’t use ReasonReact, that [@JSX] is just an attribute for hooking up macros. ReasonReact turns that agnostic createElement call into something React-specific. No standard, no extra tooling, no fuzz. Just one macro. We’ve pushed all this business from the meta-language level into the language itself.

If you’re not a fan of JSX, here’s a more mind-blowing example.

Function & Currying

I’ll assume you know what currying is. This section applies to vanilla OCaml syntax too.

The syntax sometimes shapes the semantics a whole lot. In Reason/OCaml, functions always take one, and only one, argument:

let increment = fun x => x + 1;

We simulate zero-argument function by passing an agreed-upon dummy argument called “unit”:

let logSomething = fun () => print_endline "hello!";
logSomething ();

() is syntax sugar for unit. We’ve now eliminated an edge-case (albeit often used) function calling convention while drastically simplifying the type system.

We simulate multi-arguments function call by giving a sugar to functions returning functions. This snippet:

let add = fun x => fun y => x + y;
let six = (add 5) 1;

refmt-ed, becomes:

let add = fun x y => x + y;
let six = add 5 1;

This is why you get currying for free:

let addTo5 = add 5; /* this simply returns `fun y => 5 + y */
let 6 = addTo5 1;

No complexity in the semantics nor the types. There was never any need to invent currying. We obtained multi-arguments functions by building up from a small block, rather than splitting up a big block and adding the mechanism of “currying” explicitly into a language.

Eh sure, but I can have it in my language too if I wrap the above with a nice syntax…

That’s not true for performance. The OCaml compiler (and BuckleScript) is able to “specialize” to the particular arity you’re using (proof here). So when it sees you call add 5 1, it’ll generate the code that actually applies a single function call with two arguments. Otherwise, you’d be naively allocating and calling n functions for a function of n arguments.

JS Objects

Last one! BuckleScript exposes an interop feature that allows you to write JavaScript objects directly within OCaml. Here’s what it looks like in Reason syntax:

[%bs.obj {name: "Lil Reason", age: 10}];

This’ll compile down to a JS object of the same shape, fully typed. Forget for a moment how crazily great this is; here’s what it looks like when you refmt it:

{"name": "Lil Reason", "age": 10};

And now it even looks like a JS object! We’ve decided that the macro “bs.obj” (which, again, is agnostic and entirely customizable) was used enough to first-class it as a syntax feature.

What’s the point?

Hopefully these were nice demonstrations that the discussion around syntax could a bit more advanced than the typical one surrounding whitespace correction! Especially the first and last examples: part of what Reason tries to accomplish is to insert a layer of iteration speed in-between the slow-moving language semantics and the fast-moving userland libraries. We can observe users and capture relevant patterns, upstream some into the macros, then upstream some of that into the syntax, and finally upstream the worthy parts into the language itself and remove the need for certain libraries and tools in the first place. We can also potentially go the other way around: remove deprecated features by downstreaming them into the syntax, then downstream them into macros, then into libraries, then nothing.

Try refmt-ing some code! Every time the code comes out looking slightly different, you might have learned a new (equivalent) idiom. Last example! Try formatting this:

switch foo {
| true => 1
| false => 2
}

What did you get? =)

Word of Caution

Do NOT abuse macros & other AST transforms. The more you operate on meta-code (the AST), the less intent the code itself conveys. Reason & BuckleScript curated and bundled a few ourselves through careful analysis of tradeoffs.

Have fun with Reason and see you in Discord!

Resources