25 Days of ReasonML

Will Acton
codeburst
Published in
7 min readFeb 3, 2018

--

2017 was my first year attempting Advent of Code: an annual challenge where participants tackle twenty five programming puzzles, released each day starting Dec. 1st to Dec. 25th.

I’m a mobile and front-end developer by day and work mostly with React & React Native, so Advent of Code felt like a nice change of pace: streaks of CS fundamentals ran through the problems and I frequently found myself looking up algorithms, data structures, and tackling problems I hadn’t encountered since college. Each day’s puzzle usually only took an hour or two to solve (although some of the harder problems took me more than a couple days!) and were fairly self-contained (although some later days built on the previous).

A few months before December, I had started teaching myself a new language called ReasonML. ReasonML is a statically typed language of the ML family that compiles to both JavaScript as well as native executables. Its biggest selling points are:

  • An expressive static type system with very good type inference
  • It is a functional language whose data structures are immutable by default
  • A powerful module system
  • An easy-to-use toolchain for building JavaScript applications
  • Excellent interoperability with the existing JavaScript & OCaml ecosystem
  • A syntax that looks kind of like JavaScript

Behind the scenes, the truth is that ReasonML is simply a new syntax for OCaml, a language that has been around for over 20 years. The team behind ReasonML is working on creating a suite of tools, libraries, and documentation in order to create a platform for doing modern web development.

To start, I first developed some bindings to the semi-popular library Most.js that I published as bs-most, as well as built a toy example ReasonReact app that used Most for the state management. Advent of Code seemed like it would be a wonderful opportunity to write lots of Reason code and see how it felt when developing something other than a front-end single page application.

Here are some of my takeaways from spending 25 days writing ReasonML.

1. Pattern matching feels amazing

Prior to learning ReasonML, I spent some time learning another hip-syntax-for-an-old-language Elixir, and then took some courses on Erlang itself. One of my favorite things about Erlang/Elixir was its expressive and powerful pattern matching, so I was very excited about this feature in ReasonML. I definitely went out of my way to use it in my Advent of Code solutions, and in most cases I felt like it gave me such a clear way to write down a problem that after a time it started to feel quite natural to break my program down into a series of case statements.

For example, on day 19 we had to help a packet find its way through a network. We were given a network diagram made of ASCII characters; starting at the top of the diagram, the pipe | represented a vertical portion of the path, - horizontal, and + was a joint where we would be changing direction.

While we’re following the diagram, we can use the breadcrumbs (shown as letters A..Z) to help us keep track of where we're supposed to go. Our job was the verify that the order of the breadcrumbs was the same as the expected answer.

Here’s an example:

Answer: “ABCDEF”

Parsing the diagram was done using a function that turned each symbol into a type:

Then, we can create another type to represent the direction the packet was currently moving:

To determine which direction to move to next, we base it on the current direction and what the next symbol in the path is:

The actual code is much larger, as it covers every possible case. It’s also written very clearly what happens in each case, and the compiler will make sure that every case is accounted for. Forgetting to cover a potential match will cause a compiler warning, and even tell me exactly which case I’ve missed!

2. Tail recursion rocks. So do imperative loops

For the first few days, I attempted to write my solutions in a tail recursive style as a sort of challenge. The languages I use on a day-to-day basis (JavaScript, Java) don’t do any tail-call optimizations, so it’s not really feasible to write anything that works on a large data set before your stack grows too large and your program dies.

ReasonML’s compiler toolchain does do tail call optimization, though! So we can comfortably write an infinite loop like so:

The compiler will actually turn this code into a while loop in JavaScript - which will prevent us from growing our call stack out of control. This is one of many, many optimizations that BuckleScript, the JavaScript compiler ReasonML uses in its toolchain, has to offer.

However, when the puzzles got harder, I found myself reverting back to good ol’ fashion for- and while-loops. While recursion is the more functional and (in my opinion) more readable way of writing many algorithms, there’s only a tiny bit of ceremony involved in using imperative loops in Reason:

This flexibility is powerful, and allows you to express your program in the way that best suits the problem at hand.

3. Functors are really fun

Modules are something that are very core to the design of OCaml (and by extension, ReasonML). You can think of them as the primary unit of organizing a ReasonML application; modules have a name and contain types, values and functions. E.g. we might have a Solver module that we use to solve an Advent of Code challenge:

We can then refer to the things we’ve defined in this module inside of other modules by calling Solver.cases or Solver.solve("Abcdefg").

Each ReasonML source file is considered a module; so in your code base, the things you write in Solver.re can automatically be referred to in other files like above.

Functors are an advanced feature of ReasonML and OCaml that allows you to write modules that make new modules. Similar to how a higher-order function can be given a function, and return a new function…

…we can also create higher-order modules (called Functors in OCaml) that return new modules.

The best example is the Set module that comes with the standard library. A "Set" type is similar to the mathematical notion of a set: it has an ordering (e.g. in the set of the integer numbers, 1 < 2 and 2 > 1), and elements are unique; if you have the set made from the integer numbers [1, 2, 3], and then add 1 to the set, you'll still only have [1, 2, 3]. But if you add 4 to the set you'll have [1, 2, 3, 4].

To use sets in ReasonML and OCaml, you first have to make one. You do that by calling Set.Make, which is a functor. It takes another module that must have a compare function implemented within it. E.g.:

Many built-in types have their own compare function in their module definition already, so we can usually do something like:

Cool, what’s the point?

I decided that, for Advent of Code, I would like to be able to easily create tests for my solutions. I came up with a structure that I wanted each day’s solutions to look like:

Following this structure allowed me to create a Solution functor to automatically generate a test module for each solution:

Overengineering? Sure! That’s half the fun of these projects, and gave me some insight into how we can use functors to automate things that we might normally have other tools like test runners, build tools, etc. do for us.

Of course, I didn’t stop there. I ended up setting up a hook in the compiler to run tests when relevant code changed. With BuckleScript’s blazing fast incremental compilation, and integration with Visual Studio Code, I was able to get incredibly fast feedback on my solutions without ever leaving my editor.

4. Types aren’t terrible

Finally, I just want to admit that, before trying ReasonML, I felt a bit hesitant about investing in static typing. My previous forays into statically typed languages (Java, TypeScript, and Haskell) left me wanting in terms of developer ergonomics: Java, due to the anti-DRY nature of its type system and insistence on nominal types; TypeScript, due to its ability to be both quite verbose at scale while also not adequately capturing all of the semantics that I would like to implement; and Haskell, mostly for being both incredibly terse and hard to read, as well as forcing me to not only learn its vast type system but also reason about my programs in a pure, lazy way.

This isn’t an indictment of any of those languages, but simply my experience in trying to be immediately productive with each one, and struggling. ReasonML, however, has felt incredibly smooth in it’s onboarding due to it’s focus on providing simple tools built on top of existing ecosystems (npm, OCaml) and a community who’s incredibly friendly to people of all skill levels.

In working through many of the Advent of Code challenges I found myself slipping into a mode where, before anything else, I would first start to sketch my solution with types. Once I had a skeleton of a solution, I would begin implementing it with functions and basic data structures. Many have written about leveraging types to design applications; suffice to say, it’s very interesting and offers many benefits. There were several times where after designing my solution using types, by the time I got my solution to compile, it just worked!

What’s next

I’m definitely interested in investing more of my time in learning and building things in ReasonML, and I’ve started to take an active part in the community. The discord server is probably the best place to find help or get involved, where people like Cheng Lou, Jordan Walke (creator of React), and many other incredibly kind, helpful, and intelligent people hang out.

The efforts of the ReasonML team and the community around it on building simple and effective tools cannot be overstated. The focus on developer onboarding is a huge win for people who are going to want to convince their coworkers to adopt the language as it matures. The ideas, concepts, and general attitude feels like a natural extension of all the good things that have come out of the JavaScript and web development community et. al. in the last five years. I expect to see some extraordinary things come out of ReasonML in the next five.

Originally published at gist.github.com.

--

--