Home Web Development ReasonML Quick Start Guide

ReasonML Quick Start Guide

By Raphael Rafatpanah , Bruno Joseph D'mello
books-svg-icon Book
eBook $22.99 $15.99
Print $32.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $22.99 $15.99
Print $32.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
About this book
ReasonML, also known as Reason, is a new syntax and toolchain for OCaml that was created by Facebook and is meant to be approachable for web developers. Although OCaml has several resources, most of them are from the perspective of systems development. This book, alternatively, explores Reason from the perspective of web development. You'll learn how to use Reason to build safer, simpler React applications and why you would want to do so. Reason supports immutability by default, which works quite well in the context of React. In learning Reason, you will also learn about its ecosystem – BuckleScript, JavaScript interoperability, and various npm workflows. We learn by building a real-world app shell, including a client-side router with page transitions, that we can customize for any Reason project. You'll learn how to leverage OCaml's excellent type system to enforce guarantees about business logic, as well as preventing runtime type errors.You'll also see how the type system can help offload concerns that we once had to keep in our heads. We'll explore using CSS-in-Reason, how to use external JSON in Reason, and how to unit-test critical business logic. By the end of the book, you'll understand why Reason is exploding in popularity and will have a solid foundation on which to continue your journey with Reason.
Publication date:
February 2019
Publisher
Packt
Pages
180
ISBN
9781789340785

 

Introduction to ReasonML

The last decade has seen numerous paradigm shifts in the way we build user interfaces. Web applications have moved from server-side frameworks to client-side frameworks in order to provide better user experiences. Devices and browsers have become powerful enough to run robust client-side applications, and the JavaScript language itself has seen many improvements over the years. Progressive web apps provides a native-like user experience and WebAssembly allows for native-like performance on the web platform. An increasing number of applications are being built for the browser, resulting in larger client-side codebases needing to be maintained.

During this period, several frameworks, libraries, tools, and general best practices gained and then lost popularity, resulting in JavaScript fatigue for many developers. Companies are becoming increasingly cautious in committing to newer technologies due to their impact on hiring and retaining engineering talent, as well as productivity and maintainability. It can be an expensive mistake if you introduce the wrong technology (or the right technology at the wrong time) to your team.

For many companies and developers, React has proven to be a solid choice. In 2013, Facebook made the library open source after having used it internally since 2011. They challenged us to rethink best practices (https://www.youtube.com/watch?v=DgVS-zXgMTk&feature=youtu.be) and it has since taken over frontend development (https://medium.freecodecamp.org/yes-react-is-taking-over-front-end-development-the-question-is-why-40837af8ab76). Encapsulating markup, behavior, and style into reusable components has become a huge productivity and maintainability win. The abstraction of the DOM has allowed for components to be simple, declarative functions of its props that are easy to reason about, compose, and test.

Via React, Facebook has done an incredible job educating the frontend-developer community on traditional functional programming paradigms that make it easier to reason about and maintain code. And now, Facebook believes the time is right for ReasonML.

This is a two-year chart from npmtrends.com that shows the number of weekly npm downloads for some of the top JavaScript libraries and frameworks. ReactJS looks to be a clear winner and has reached over 2,500,000 downloads per week:

npmtrends.com

In this chapter, we'll do the following:

  • Discuss what ReasonML is and what problems it tries to solve
  • Understand some of the reasons why Facebook chose ReasonML as the future of ReactJS
  • Experiment with ReasonML in an online playground and examine its compiled (JavaScript) output
 

What is ReasonML?

Reason is a layer of syntax & tooling on top of OCaml, a language Facebook uses actively. Jordan [Walke] started the concept of Reason before React, in fact. We’re taking it and using it as an actual frontend language (among other uses) because we think that after three and half years, the React experiment has succeeded and people are now ready for Reason...

Let's expand on this quote. ReasonML is not a new language; it's a new syntax for the OCaml language that is meant to be familiar to JavaScript developers. Reason, as we'll call it from now on, has the exact same AST as OCaml, so Reason and OCaml only differ by syntax. The semantics are the same. By learning Reason, you're also learning OCaml. In fact, there's a command-line tool that converts between OCaml and Reason syntax, called refmt, which formats Reason/OCaml code similar to JavaScript's prettier—in fact, prettier was inspired by refmt.

OCaml is a general-purpose programming language with an emphasis on expressiveness and safety. It was initially released in 1996 and has an advanced type system that helps catch your mistakes without getting in the way. Like JavaScript, OCaml features garbage collection for automatic memory management and first-class functions that can be passed around as arguments to other functions.

Reason is also a toolchain that makes getting started easier for those coming from a JavaScript background. This toolchain allows us to take advantage of both the JavaScript and OCaml ecosystems. We will dive deeper here in Chapter 2, Setting Up a Development Environment. For now, we'll experiment directly in the online playground by visiting Reason's online playground at https://reasonml.github.io/try.

Try typing in this Hello World example into the online playground:

let message = "World";
Js.log("Hello " ++ message);

There are two things you'll notice:

  • The OCaml syntax is automatically generated in the lower-left section of the editor (not shown)
  • The Reason/OCaml code is compiled to JavaScript directly in the browser:
// Generated by BUCKLESCRIPT VERSION 3.2.0, PLEASE EDIT WITH CARE
'use strict';


var message = "World";

console.log("Hello World");

exports.message = message;
/* Not a pure module */

You may be wondering how the Reason/OCaml code is being compiled from within the browser. BuckleScript, Reason's partner project, compiles the OCaml AST to JavaScript. Since Reason and OCaml both get converted into the same OCaml AST, BuckleScript supports both Reason and OCaml. Furthermore, since BuckleScript is itself written in OCaml, it can be compiled to JavaScript and run directly in the browser.

Inspecting the compiled JavaScript reveals just how readable it is. Looking closer, you'll notice that the compiled output has also been optimized: within the console.log statement, the "Hello World" string has been inlined directly instead of using the message variable.

BuckleScript, using features of the OCaml type-system and compiler implementation is able to provide many optimizations during offline compilation, allowing the runtime code to be extremely fast.

Notably, BuckleScript also supports string interpolation (https://bucklescript.github.io/docs/en/common-data-types.html#interpolation):

/* The message variable is interpolated */
{j|Hello $message|j}
 

Why Reason?

What makes Reason so compelling? What can Reason do that TypeScript or Flow cannot? Is it just about having a static type-checker? These are some of the questions I had when first getting started with Reason.

Support for immutability and purity

Reason isn't just about having a static type system. Also important is the fact that Reason is immutable by default. Immutability is an important concept in functional programming. In practice, using immutable data structures (data structures that can't change) results in safer, easier-to-reason-about, and more maintainable code than their mutable counterparts. This will be a recurring theme throughout this book.

Purity is another important concept in functional programming. A function is said to be pure if its output is determined only by its input, without observable side-effects. In other words, a pure function doesn't do anything outside of returning a value. The following is an example of a pure function:

let add = (a, b) => a + b;

And, this is an example of an impure function:

let add = (a, b) => {
Js.log("side-effect");
a + b;
};

The side-effect in this case is writing to the browser's console. That's why, in our preceding Hello World example, BuckleScript included the /* Not a pure module */ comment at the end of the compiled output.

Mutating a global variable is also a side-effect. Consider the following JavaScript:

var globalObject = {total: 0};
const addAndMutate = (a, b) => globalObject.total = a + b;
addAndMutate(40, 2);
/* globalObject now is mutated */

The global object was mutated, and now its total property is 42. We now have to be aware of all areas where this globalObject is mutated whenever using it. Forgetting that this object is both global and mutable can lead to hard-to-debug problems. One idiomatic solution to this problem is to move globalObject into a module where it's no longer global. This way, only that module has access to it. However, we'd still need to be aware of all areas within this module that can update the object.

If globalObject was immutable instead, there would be no way to mutate it. Therefore, we wouldn't need an awareness of all the areas that can mutate globalObject, since there wouldn't be any of these areas. We'll see that, with Reason, it's fairly simple and natural to build real applications in this way by creating updated copies of the original data. Consider the following:

let foo = 42;
let foo = foo + 1;
Js.log(foo);
/* 43 */

The syntax feels quite natural. As we'll see later in this book, immutability—changing by returning updated copies instead of applying destructive changes in place—fits the React/Redux way of doing things quite well.

The original foo was not mutated; it was shadowed. Once shadowed, the old foo binding is unavailable. Bindings can be shadowed in local scopes as well as global scopes:

let foo = 42;

{
let foo = 43;
Js.log(foo); /* 43 */
};

Js.log(foo); /* 42 */

let foo = 43;
Js.log(foo); /* 43 */

Trying to mutate foo results in a compilation error:

let foo = 42;
foo = 43;
/* compilation error */

We can see that immutability and purity are related topics. Having a language that supports immutability allows you to program in a pure way without side-effects. However, what if there are times when purity would cause the code to become more complex and harder to reason about than using side-effects? You may be relieved to learn that Reason (interchangeable with OCaml throughout the rest of this book) is a pragmatic language that let's us cause side-effects when needed.

The key thing when using a language like [Reason] is not to avoid side-effects, because avoiding side-effects is equivalent to avoiding doing anything useful. It turns out, in reality, programs don't just compute things, they do things. They send messages and they write files and they do all sorts of stuff. The doing of things is automatically involving side-effects. The thing that a language which supports purity gives you, is it gives you the ability to, by and large, segment out the part that is side-effecting to clear and controlled areas of your code, and that makes it much easier to reason about.

It's also important to know that immutability doesn't come at the cost of performance. Under the hood, there are optimizations in place that keeps Reason's immutable data structures fast.

Module system

Reason has a sophisticated module system that allows for modular development and code organization. All modules are globally available in Reason, and module interfaces can be used to hide implementation details when needed. We will be exploring this concept in Chapter 5, Effective ML.

Type system

Reason's type system is sound, which means that, once compiled, there won't be runtime type errors. There is no null in the language, nor are there any bugs related to null. In JavaScript, when something is of the number type, it can also be null. Reason uses a special type for things that can also be null, and forces the developer to handle those cases appropriately by refusing to compile otherwise.

So far, we've already written some, albeit basic, Reason code without even talking about types. Reason infers types automatically. As we'll learn throughout this book, the type system is a tool that provides guarantees without getting in our way, and when used properly, can allow us to offload things to the compiler that we used to keep in our heads.

Reason's support for immutable programming, sound type system, and sophisticated module system are big parts of why Reason is so great, and there's something to be said about using all of these features together in one language that was built with these features in mind. When Facebook initially released React, they asked us to give it five minutes (https://signalvnoise.com/posts/3124-give-it-five-minutes) and, hopefully, that same frame of mind will pay off here as well.

Cross-platform

Building React applications with Reason is a lovely experience and, what's more, since OCaml is able to compile to native, we will be able to use these same skills to build apps that compile to assembly, iOS/Android, and much more. In fact, Jared Forsyth has already created a game called Gravitron (https://github.com/jaredly/gravitron) that compiles to iOS, Android, web, and macOS from one Reason codebase. That being said, the frontend JavaScript story is much more polished as of this writing.

Maintainability

Reason may take some time to get comfortable with, but you can think of this time as an investment in the maintenance and confidence of your future product. Although languages with gradual type systems, such as TypeScript, may be easier to get started with, they don't provide the sorts of guarantees that a sound type system such as Reason's can provide. Reason's true benefits cannot be completely conveyed within simple examples, and only really shine when they save you time and energy in reasoning about, refactoring, and maintaining your code. Put it this way; if someone told me they were 99% sure a spider wasn't in my bed, I would still have to check the entire bed because I don't like bugs!

As long as you're 100% in Reason and your code compiles, the type system guarantees there will be no runtime type errors. It's true that when you are interoperating with non-Reason code (JavaScript, for example), you introduce the possibility of runtime type errors. Reason's sound type system allows you to trust that the Reason parts of the application won't cause runtime type errors, which therefore allows you to focus extra attention on ensuring that these areas of the application are safe. In my experience, programming in a dynamic language can feel noticeably dangerous. Reason on the other hand feels like it always has your back.

Interoperability

That being said, sometimes—and especially when first learning about type systems—you may be unsure as to how to get your code to compile. Reason, through BuckleScript, allows you to drop down to raw JavaScript when you need to, either via bindings or directly inside your Reason (.re) files. This gives you the freedom to figure things out as you go along in JavaScript, and then once you're ready, convert that section of the code to type-safe Reason.

BuckleScript also lets us bind to idiomatic JavaScript in a very reasonable way. As you'll learn in Chapter 4, BuckleScript, Belt, and Interoperability, BuckleScript is an incredibly powerful part of Reason.

ES2030

Community

The Reason community is, hands down, one of the most helpful, supportive, and inclusive communities I've ever been a part of. If you have a question, or are stuck on something, the Reason Discord channel is the place to go for realtime support.

Reason Discord channel:

https://discord.gg/reasonml

Often, when starting with a new technology, talking to someone with experience for five minutes can save you hours of frustration. I've personally asked questions at all hours of the day (and night) and am so incredibly grateful for and amazed by how quickly someone helps me out. Take a moment to join the Discord channel, introduce yourself, ask questions, and share your feedback on how to make Reason better!

The Future of ReactJS

In practice, few real-world applications use just ReactJS. Additional technologies, such as Babel, ESLint, Redux, Flow/TypeScript, and Immutable.js, are typically brought in to help increase the maintainability of a codebase. Reason replaces the need for these additional technologies with its core language features.

ReasonReact is a Reason library that binds to ReactJS and provides a simpler, safer way to build ReactJS components. Just like ReactJS is just JavaScript, ReasonReact is just Reason. Additionally, it's easy to incrementally adopt because it was made by the same person who created ReactJS.

ReasonReact comes with a built in router, Redux-like data management, and JSX. You'll feel quite at home coming from a ReactJS background.

It's important to mention that Reason/ReasonReact is already being used by several companies in production, including within one of the largest codebases in the world. Facebook's messenger.com codebase is already over 50% converted to ReasonReact.

Every ReasonReact feature has been extensively tested on the messenger.com codebase.

As a result, new releases of Reason and ReasonReact come with code mods that automate much, if not all, of the upgrade process for your code base. New features are thoroughly tested internally at Facebook before they're released to the public, and this results in a pleasant developer experience.

 

Exploring Reason

Ask yourself whether the following is a statement or an expression:

let foo = "bar";

In JavaScript, it's a statement, but in Reason, it's an expression. Another example of an expression is 4 + 3, which can also be represented as 4 + (2 + 1).

Many things in Reason are expressions, including control structures such as if-else, switch, for and while:

let message = if (true) {
"Hello"
} else {
"Goodbye"
};

We also have ternaries in Reason. Here is another way to express the preceding code:

let message = true ? "Hello" : "Goodbye";

Even anonymous block scopes are expressions that evaluate to the last line's expression:

let message = {
let part1 = "Hello";
let part2 = "World";
{j|$part1 $part2|j};
};
/* message evaluates to "Hello World" */
/* part1 and part2 are not accessible here */

A tuple is an immutable data structure that can hold different types of values and can be of any length:

let tuple = ("one", 2, "three");

Let's use what we know so far and dive right in with the FizzBuzz example from Reason's online playground. FizzBuzz was a popular interview question to determine whether a candidate is able to code. The challenge is to write a problem that prints the numbers from 1 to 100, but instead prints Fizz for multiples of three, Buzz for multiples of five, and FizzBuzz for multiples of both three and five:

/* Based on https://rosettacode.org/wiki/FizzBuzz#OCaml */
let fizzbuzz = (i) =>
switch (i mod 3, i mod 5) {
| (0, 0) => "FizzBuzz"
| (0, _) => "Fizz"
| (_, 0) => "Buzz"
| _ => string_of_int(i)
};

for (i in 1 to 100) {
Js.log(fizzbuzz(i))
};

Here, fizzbuzz is a function that accepts an integer and returns a string. An imperative for loop logs its output to the console.

In Reason, a function's last expression becomes the function's return value. The switch expression is the only fizzbuzz expression, so whatever that evaluates to becomes the output of fizzbuzz. Like JavaScript, the switch evaluates an expression and the first matched case gets its branch executed. In this case, the switch evaluates the tuple expression: (i mod 3, i mod 5).

Given i=1, (i mod 3, i mod 5) becomes (1, 1). Since (1, 1) isn't matched by (0, 0), (0, _), or (_, 0), in that order, the last case of _ (that is, anything) is matched, and "1" is returned. Similarly, fizzbuzz returns "2" when given i=2. When given i=3, "Fizz" is returned.

Alternatively, we could have implemented fizzbuzz using if-else:

let fizzbuzz = (i) =>
if (i mod 3 == 0 && i mod 5 == 0) {
"FizzBuzz"
} else if (i mod 3 == 0) {
"Fizz"
} else if (i mod 5 == 0) {
"Buzz"
} else {
string_of_int(i)
};

However, the switch version is much more readable. And as we'll see later in this chapter, the switch expression, also called pattern matching, is much more powerful than we've seen so far.

Data structures and types

A type is a set of values. More concretely, 42 has the int type because it's a value that's contained in the set of integers. A float is a number that includes a decimal point, that is, 42. and 42.0. In Reason, integers and floating point numbers have separate operators:

/* + for ints */
40 + 2;

/* +. for floats */
40. +. 2.;

The same is true for -., -, *., *, /., and /.

Reason uses double quotes for the string type and single quotes for the char type.

Creating our own types

We can also create our types:

type person = (string, int);

/* or */

type name = string;
type age = int;
type person = (name, age);

Here's how we create a person of the person type:

let person = ("Zoe", 3);

We can also annotate any expression with its type:

let name = ("Zoe" : string);
let person = ((name, 3) : person);

Pattern matching

We can use pattern matching on our person:

switch (person) {
| ("Zoe", age) => {j|Zoe, $age years old|j}
| _ => "another person"
};

Let's use a record instead of a tuple for our person. Records are similar JavaScript objects except they're much lighter and are immutable by default:

type person = {
age: int,
name: string
};

let person = {
name: "Zoe",
age: 3
};

We can use pattern matching on records too:

switch (person) {
| {name: "Zoe", age} => {j|Zoe, $age years old|j}
| _ => "another person"
};

Like JavaScript, {name: "Zoe", age: age} can be represented as {name: "Zoe", age}.

We can create a new record from an existing one using the spread ( ... ) operator:

let person = {...person, age: person.age + 1};

Records require type definitions before they can be used. Otherwise, the compiler will error with something like the following:

The record field name can't be found.

A record must be the same shape as its type. Therefore, we cannot add arbitrary fields to our person record:

let person = {...person, favoriteFood: "broccoli"};

/*
We've found a bug for you! This record expression is expected to have type person The field favoriteFood does not belong to type person
*/

Tuples and records are examples of product types. In our recent examples, our person type required both an int and an age. Almost all of JavaScript's data structures are product types; one exception is the boolean type, which is either true or false.

Reason's variant type, which is an example of a sum type, allows us to express this or that. We can define the boolean type as a variant:

type bool =
| True
| False;

We can have as many constructors as we need:

type decision =
| Yes
| No
| Maybe;

Yes, No, and Maybe are called constructors because we can use them to construct values. They're also commonly called tags. Because these tags can construct values, variants are both a type and a data structure:

let decision = Yes;

And, of course, we can pattern match on decision:

switch (decision) {
| Yes => "Let's go."
| No => "I'm staying here."
| Maybe => "Convince me."
};

If we were to forget to handle a case, the compiler would warn us:

switch (decision) {
| Yes => "Let's go."
| No => "I'm staying here."
};

/*
Warning number 8 You forgot to handle a possible value here, for example: Maybe
*/

As we'll learn in Chapter 2, Setting Up a Development Environment, the compiler can be configured to turn this warning into an error. Let's see one way to help make our code more resilient to future refactors by taking advantage of these exhaustiveness checks.

Take the following example where we are tasked with calculating the price of a concert venue's seat given its section. Floor seats are $55, while all other seats are $45:

type seat =
| Floor
| Mezzanine
| Balcony;

let getSeatPrice = (seat) =>
switch(seat) {
| Floor => 55
| _ => 45
};

If, later, the concert venue allows the sale of seats in the orchestra pit area for $65, we would first add another constructor to seat:

type seat =
| Pit
| Floor
| Mezzanine
| Balcony;

However, due to the usage of the catch-all _ case, our compiler doesn't complain after this change. It would be much better if it did since that would help us during our refactoring process. Stepping through compiler messages after changing type definitions is how Reason (and the ML family of languages in general) makes refactoring and extending code a safer, more pleasant process. This is, of course, not limited to variant types. Adding another field to our person type would also result in the same process of stepping through compiler messages.

Instead, we should reserve using _ for an infinite number of cases (such as our fizzbuzz example). We can refactor getSeatPrice to use explicit cases instead:

let getSeatPrice = (seat) =>
switch(seat) {
| Floor => 55
| Mezzanine | Balcony => 45
};

Here, we welcome the compiler nicely informing us of our unhandled case and then add it:

let getSeatPrice = (seat) =>
switch(seat) {
| Pit => 65
| Floor => 55
| Mezzanine | Balcony => 45
};

Let's now imagine that each seat, even ones in the same section (that is, ones that have the same tag) can have different prices. Well, Reason variants can also hold data:

type seat =
| Pit(int)
| Floor(int)
| Mezzanine(int)
| Balcony(int);

let seat = Floor(57);

And we can access this data with pattern matching:

let getSeatPrice = (seat) =>
switch (seat) {
| Pit(price)
| Floor(price)
| Mezzanine(price)
| Balcony(price) => price
};

Variants are not just limited to one piece of data. Let's imagine that we want our seat type to store its price as well as whether it's still available. If it's not available, it should store the ticket holder's information:

type person = {
age: int,
name: string,
};

type seat =
| Pit(int, option(person))
| Floor(int, option(person))
| Mezzanine(int, option(person))
| Balcony(int, option(person));

Before explaining what the option type is, let's have a look at its implementation:

type option('a)
| None
| Some('a);

The 'a in the preceding code is called a type variable. Type variables always start with a '. This type definition uses a type variable so that it could work for any type. If it didn't, we would need to create a personOption type that would only work for the person type:

type personOption(person)
| None
| Some(person);

What if we wanted an option for another type as well? Instead of repeating this type declaration over and over, we declare a polymorphic type. A polymorphic type is a type that includes a type variable. The 'a (pronounced alpha) type variable will be swapped with person in our example. Since this type definition is so common, it's included in Reason's standard library, so there's no need to declare the option type in your code.

Jumping back to our seat example, we store its price as an int and its holder as an option(person). If there's no holder, it's still available. We could have an isAvailable function that would take a seat and return a bool:

let isAvailable = (seat) =>
switch (seat) {
| Pit(_, None)
| Floor(_, None)
| Mezzanine(_, None)
| Balcony(_, None) => true
| _ => false
};

Let's take a step back and look at the implementations of getSeatPrice and isAvailable. It's a shame that both functions need to be aware of the different constructors when they don't have anything to do with the price or availability of the seat. Taking another look at our seat type, we see that (int, option(person)) is repeated for each constructor. Also, there isn't really a nice way to avoid using the _ case in isAvailable. These are all signs that another type definition might serve our needs better. Let's remove the arguments from the seat type and rename it section. We'll declare a new record type, called seat, with fields for section, price, and person:

type person = {
age: int,
name: string,
};

type section =
| Pit
| Floor
| Mezzanine
| Balcony;

type seat = {
section, /* same as section: section, */
price: int,
person: option(person)
};

let getSeatPrice = seat => seat.price;

let isAvailable = seat =>
switch (seat.person) {
| None => true
| Some(_person) => false
};

Now, our getSeatPrice and isAvailable functions have a higher signal-to-noise ratio, and don't need to change when the section type changes.

As a side note, _ is used to prefix a variable to prevent the compiler from warning us about the variable being unused.

Making Invalid States Impossible

Let's say that we'd like to add a field to seat to hold the date a seat was purchased:

type seat = {
section,
price: int,
person: option(person),
dateSold: option(string)
};

Now, we've introduced the possibility of an invalid state in our code. Here's an example of such a state:

let seat = {
section: Pit,
price: 42,
person: None,
dateSold: Some("2018-07-16")
};

In theory, the dateSold field should only hold a date when the person field holds a ticket holder. The ticket has a sold date, but no owner. We could look through our imaginary implementation to verify that this state would never happen, but there would still be the possibility that we missed something, or that some minor refactor introduced a bug that was overlooked.

Since we now have the power of Reason's type system at our disposal, let's offload this work to the compiler. We are going to use the type system to enforce invariants in our code. If our code breaks these rules, it won't compile.

One giveaway that this invalid state could exist is the use of option types within our record field. In these cases, there may be a way to use a variant instead such that each constructor only holds the relevant data. In our case, our sold-date and ticket-holder data should only exist when the seat has been sold:

type person = {
age: int,
name: string,
};

type date = string;

type section =
| Pit
| Floor
| Mezzanine
| Balcony;

type status =
| Available
| Sold(date, person);

type seat = {
section,
price: int,
status
};

let getSeatPrice = (seat) => seat.price;

let isAvailable = (seat) =>
switch (seat.status) {
| Available => true
| Sold(_) => false
};

Check out our new status type. The Available constructor holds no data, and Sold holds the sold date as well as the ticket holder.

With this seat type, there's no way to represent the previous invalid state of having a sold date without a ticket holder. It's also a good sign that our seat type no longer includes option types.

 

Summary

In this chapter, we got a feel for what Reason is and what problems it tries to solve. We saw how Reason's type inference removes much of the burden associated with statically-typed languages. We learned that the type system is a tool that can be used to provide codebases with powerful guarantees that provide an excellent developer experience. While Reason may take some time to get used to, it's well worth the investment for medium-sized to larger codebases.

In the next chapter, we'll learn about Reason's toolchain when we set up our development environment. In Chapter 3, Creating ReasonReact Components, we'll start to build an application that we'll use throughout the rest of this book. By the end of this book, you'll be comfortable building real-world React applications in Reason.

About the Authors
  • Raphael Rafatpanah

    Raphael Rafatpanah is a husband and father who wonders how he's going to pique his three-year-old daughter's interest in programming. With a background in business and math, he got started writing software to automate the data entry process at his family's business. Now, he's passionate about frontend development and the web platform. In his spare time, you'll find him searching for excellent sushi, photographing the world, and working on the elusive side project.

    Browse publications by this author
  • Bruno Joseph D'mello

    Bruno Joseph D'mello is proactively working at Truckx as a full stack developer. He is a JavaScript enthusiast and loves working with open source communities. He possesses more than 6 years' experience in web development. Bruno follows kaizen and enjoys the freedom of architecting new things at work. He is socially active via coaching in web technologies and participating in other research projects and meetups. When not engaged with technology, Bruno likes to spend quality time traveling with family and friends.

    Browse publications by this author
Latest Reviews (1 reviews total)
Superb
ReasonML Quick Start Guide
Unlock this book and the full library FREE for 7 days
Start now