Getting Started with Reason and BuckleScript

Steven Scaffidi
9 min readJan 16, 2018

--

In this post, I’m going to run you through some of the core concepts in Reason, and I’ll use the web bindings to make a simple accordion. By the end of this post you should be able to write simple Reason code with a basic understanding of the syntax and type system.

So what are Reason and BuckleScript? A thorough answer to this question can be found on Reason’s website; however, I’ll summarize it for the purpose of this article. Reason is new syntax and a toolchain for OCaml developed by Facebook. The syntax and workflow are very familiar to those coming from JavaScript so getting started feels very familiar. As a bonus, it also has first class support for React through ReactReason (note - I won’t talk about ReactReason in this post). BuckleScript is a partner project that allows you to compile Reason (or OCaml) into readable JavaScript. Reason, like OCaml, is statically typed (in fact it provides 100% type coverage) and stupid fast (truly, look it up).

Starting a new Project

Getting started with Reason is easy. First, go through the instructions for setting up your editor on Reason’s website. This is critical to your success with Reason as the editor plugins can be a lifesaver (especially for those new to the language).

Assuming that your editor is set up, let’s install BuckleScript by running the following:

npm install -g bs-platform

Then let’s start a new project. I’m going to call mine reason-accordion since that’s what I’ll ultimately be making.

bsb -init reason-accordion -theme basic-reason

If you cd into reason-accordion directory, you can run npm start to get started. Once you’re up and running, you’ll notice that BuckleSript is actually watching for file changes in the src directory. This is all configured through the bsconfig.json. I rename my demo.re file to accordion.re for clarity. Then, create an index.html in the root directory (just for simplicity) with the following content:

<!DOCTYPE html>
<html lang="en">
<head>
<title>Accordion</title>
</head>
<body>
<script src="src/accordion.bs.js"></script>
</body>
</html>

When we run npm start, BuckleScript will output the file accordion.bs.js for us. I’ll be using the web-api bindings for Reason later in this article. This will introduce require statements into our JS file. So I have a couple of options, the easiest of which is to use parcel. Parcel will deal with those require and export statements without any configs. Here is a basic setup:

npm i -g parcel
npm start
parcel index.html

Now you should be able to go to localhost:1234 and see a blank document. In the console, you should see “Hello, BuckleScript and Reason!” logged. To round out the setup, install the web bindings for Reason.

npm i bs-webapi

In the bsconfig.json, replace the following line:

"bs-dependencies": ["bs-webapi"]

Now we can use the web-binds in our Reason code. No other configs or setup is necessary.

Starting with Reason

For the remainder of this post, I’ll just be messing with the accordion.re file. When we make changes to this file, BuckleScript will change accordion.bs.js accordingly. Since BuckleScript outputs JS that is readable, it can be helpful to see what BuckleScript outputs for given Reason code from time to time. Before jumping into the web-api, I’ll go over some simple syntax we’ll need in order to make the accordion. It’s important to note that a semi-colon to end lines is not optional in Reason.

Let Binding

The let binding allows us to assign variables. Like const and let in JavaScript, it is block scoped and like const bindings are immutable. Here is a simple example:

Notice that your editor informs you that myMessage is in fact a string. The type hints from the editor is going to help you get used to the language quickly.

Functions

By design, Reason is a functional language. Syntactically, functions look similar to arrow functions in JavaScript. Here is a simple example:

The editor tells me ('a) => unit. So what is a unit and what does 'a mean? 'a is just another way of saying that the function can accept any type. We didn’t define the type logMessage accepts instead it’s implicitly defined from Js.log (if you hover over log on your editor you’ll notice it has the same definition). If we wanted logMessage to only accept a type of string then we can change the definition to let logMessage = (str: string) => Js.log(str);. Now Js.log and logMessage have different signatures. If you try to call logMessage(1); two things happen. The first thing is that both BuckleScript and your editor will tell you that a bug has been found. The message says that logMessage was called with int but expected string. The second thing that’ll happen is that BuckleScript won’t actually compile to JS. Once you fix your error, then BuckleScript will output the new JS.

One more thing about functions, currying is free. In fact every function in Reason is curried. Here is an example with a simple addition function:

Side note: Our editor tells us that this has a signature of (int, int) => int. The implicit typing is hugely powerful. I didn’t define the type of x or y, but based on the function it’s still strictly typed. Reason has 100% type coverage at all times, but that doesn’t mean that you have to define every single type.

We can obviously call addNumbers(5, 3) and that’ll give us 8; however, we can call this differently. For example, add Js.log(addNumbers(5));. In the browser console you’ll see function (param) { return 5 + param | 0; }. This is the power of automatic currying! So let’s get into other ways that we can call addNumbers:

Wait, what is the pipeline operator? The pipeline operator allows us to write g(f(x) as x |> f |> g which is a powerful, elegant way to take advantage of Reason currying.

Side Note: The pipeline operator is actually available in JavaScript (as long as you’re using Babel Stage 1). See https://github.com/tc39/proposal-pipeline-operator.

Variants

I’m going to start writing the accordion from here on out. You can follow along with me by erasing everything you have in your accordion.re file and stepping through the rest of the post.

I’m going to write a simple variant keeping in mind our accordion example. Our Reason file already knows about our webapi dependency (just start typing Webapi and you’ll see that it starts to autofill); however, in order to streamline the process of using it for this tutorial, we are going to use open. open allows us to use the API without having to prefix every declaration with Webapi.Dom.{whatever} every time we use it. I’ll just write open Webapi.Dom; at the top of our file.

Side note: You want to keep the use of open to a minimum. It introduces a namespace to your file that could result in a potential name clash. Since our example is based around the Webapi, and we are going to use it heavily, it’s fine in this instance.

I’ve defined an action type with 2 variants Expand and Collapse. Both variants accept a Dom.element (i.e. the DOM element that we’ll be setting a max height for) and a string (i.e. the max height of the variant). Notice that I can conceptualize a good deal of my app through types without writing the actual code.

Making the Accordion

We’re going to use our new action variant to create a new function that handles it.

toggle is a function that accepts an action and returns a unit. It pattern matches the variant in action.switch is how we match the pattern. It’s similar to other switch statements in other languages. If you exclude the Exclude or Collapse action in the switch statement, Reason will warn you that the switch statement is not exhaustive. Each case statement does two things, it sets a style attribute on the element passed and adds and removes some classes. There are a couple of custom functions in here getClassList and toggleClasses.

Notice how we are using the pipeline operator and the fact that all functions are curried in Reason. This leads to elegant readable code. To see how this works, hover over Element.classList and notice that it’s a function. That’s the way most APIs will work in Reason. If you’re coming from JavaScript, you’re going to have to get used to the fact that almost everything in Reason is a function. Element.classList is a function that accepts a Dom.element. I’m able to see this in my editor by hovering over classList. Let’s rewrite the last line of each case in order to better understand what’s happening:

This is a relatively simple chaining of functions. Imagine if we had a much longer composition. Using the |>, it’s easy to see how these functions come together.

Now we have our action complete. All we have to do now is select some elements from the DOM, add an event listener and pass it a variant based on the classes of the event target.

For the remainder of the example, we’ll pretend like we have the following structure of HTML in our index.html file:

...
<body>
<div class="accordion">
<div class="section">
<div class="header"></div>
<div class="content"></div>
</div>
<div class="section">
<div class="header"></div>
<div class="content"></div>
</div>
</div>
</body>
...

You’ll want to put some text or images in the header and content classes in order to visualize what’s happening.

Let’s first select our accordion from the DOM:

I’ll also define unwrapElement:

What is this? Well, here is another thing about Reason: the type system is 100% sound. This means that if x has a type int, x will never be any other type including null. That means a pure Reason program guarantees that you’ll never have a null error!

Getting back to the above example, Document.querySelector actually returns option(Dom.element). option is a built-in variant that has two cases Some('a) and None. You probably want to handle each case more elegantly than I did above (the None case simply returns an error), but it’s fine for the purposes of this example. Note that each case in any variant must return the same type. raise returns 'a which is any type and thus is why the above works.

Now let’s select our section of the accordion:

Note that Element.querySelectorAll returns a Dom.nodeList so it doesn’t need to be unwrapped. Using the sections selected above, we can make the real guts of the application.

On a high level let’s go over what we are doing here.

  1. We are turning our NodeList into an array.
  2. We are mapping over our array with composeItem. composeItem returns to us an Array of elements in our accordion.

There are a couple of types and methods missing from the above function. I’ve added these:

Here is an explanation of the above code snippet.

  1. asDomElement uses Reason’s interop to convert (cast) any type into a DOM Element. Per Glenn on Reason’s discord, the DOM so dynamically typed that it’s impossible to coerce it’s actual type. As a result, we resort to using asDomElement and in this particular case there is nothing wrong with doing so.
  2. accordionItem is a record. A record is like a JavaScript object except that it’s immutable and has fixed field names and type. If you look at the signature of composeItem it actually returns an accordionItem. Reason was able to “reason” that the last part of composeItem was in fact an accordionItem based solely on the composition.
  3. getSectionElement and getHeight are simple utility functions that are fairly straight forward.
  4. handleHeaderClick is our event handler. Notice how composeItem adds this event handler with addClickEventListener. There is the more generic addEventListener, but this has a more generic Dom.event type which you must then convert to a Dom.mouseEvent. So Reason’s webapi makes this a little easier for us.
    Side Note: You can read more about this issue here.

Finishing It Up

The above code helped us create a simple accordion (given you’ve add a little CSS for the transition to your index.html). Here a screen capture of the final example.

Hopefully this post helps you get started with using Reason. A good precursor to Reason is having a decent understanding of functional programming and the patterns associated with it. If you’re coming from a JavaScript background Functional Light by Kyle is a good place to start. The Reason docs are also a great resource with many examples to help you along the way. Reason has a great, growing community, and it’s a fun time to be part of such a new language. You can join the conversation on the Reason Discord channel.

My source code is available on Github. I always love hearing feedback about my work. Let me know if you have any questions, and I’ll be happy to help out. Thanks for reading.

--

--

Steven Scaffidi

Full Stack @reactjs @reactnative @feathersjs @expressjs ReasonML developer | Tulane MBA @freemanschool | Husband | Rescue Dad