anmonteiro Code ramblings

Shipping a (very simplistic) ReasonReact app

I used Reason and React to build a simple example app: ReKeys. Here's what I learned.

I've been enamored with ML family of programming languages for a while. However, and aside from the occasional Kata, I had never really shipped anything using an ML language.

Given the recent activity in the Ocaml community, however – mostly due to the communication efforts behind Facebook's Reason – I felt compelled to give it another try (after a very short incursion during my undergrad), and I was in for a treat!

Getting Started & Reading Material

I went through Reason's initial setup right around the time Jared Forsyth published a very detailed tutorial about getting started with ReasonReact and compiling Reason for the browser using BuckleScript.

Despite its young age, BuckleScript is an incredible piece of technology that will (instantly, if I may add) compile Ocaml code (with built-in) support for Reason, to JavaScript. The BuckleScript manual, though somewhat terse in the beginning, is an amazing reference to working with the compiler and has become a pinned tab on my browser right after I started this journey.

Next steps & Troubleshooting

But after the initial setup I was on my own, and I had to start somewhere. reason-scripts, a custom template for create-react-app, turned out to be a great way to bootstrap my project without the hassle of writing boilerplate code, setting up tooling and spending hours figuring out the tiniest of mistakes.

As any beginner going through the hurdles of unfamiliar paradigms, it wasn't long until I hit some difficulties. What follows is an attempt to document the pitfalls that trapped me at first, which I hope will be useful for others trying to figure out rookie mistakes as they get started with Reason(React).

The dreadful type variable cannot be generalized

In his tutorial, Jared describes making a stateful component, but when I attempted to migrate my stateless component to a stateful one, I was immediately greeted by the following, somewhat cryptic, error message:

Module build failed: Error: File "/path/to/src/app.re", line 5, characters 16-51:
Error: The type of this expression,
       ('_a, ReasonReact.stateless, ReasonReact.noRetainedProps,
        ReasonReact.noRetainedProps)
       ReasonReact.componentSpec,
       contains type variables that cannot be generalized

The solution was simply to use state somewhere in any function of my component. Even just destructuring state from the e.g. the single argument to render solves the problem.

Now the reason why this happens is quite interesting: if you look at the type of ReasonReact.statefulComponent, there's one type variable 'state. That's the variable that the error is referring to: when state is not used within the component definition, the compiler can't infer what we want its type to be. If used explicitly, then we're literally telling what that type variable should be.

An interesting note here is that ReasonReact.statelessComponent doesn't have this problem. If we look at its type definition, it's almost immediately obvious why: there are no type variables in sight. The compiler always knows that it will take a stateless argument (which is defined above in the file as being the unit type).

The team behind ReasonReact is well aware of this error and some other edge cases in the library, and actively working to fix them in the near future. For more information, there's a section about this pitfall in the Ocaml FAQ.

Modules & Capitalization

Ocaml has a very interesting module system. In short, modules are used to group together related definitions, and can be arbitrarily nested. Interestingly enough, files too become modules, and one of my earliest mistakes was related to their capitalization when opening a module from a different file.

As part of ReKeys, I define a file called dom_utils.re for grouping certain definitions related to interaction with the DOM and events. When trying to open this file for consumption in another file, I couldn't get it to work.

The reason is that the module provided by a file is recognized by the compiler with the first letter (and only the first letter) capitalized. So domUtils.re becomes DomUtils, but dom_utils.re becomes Dom_utils and I was trying to open Dom_Utils. This is one of those mistakes that I'll never make again, but it was a head scratcher for a while there!

Inline type signatures

Having tried other languages in the ML family such as Haskell, Elm or PureScript, I struggled initially with how to annotate the types of my definitions. In Haskell, for example, types can be annotated above a function's implementation, like below:

foo :: Int -> Int
foo x = x + 1

In Ocaml/Reason, however, annotating the foo function would either be done inline, or in a .rei interface file. Example:

/* my_file.rei */
let foo: int => int;

/* inline, in a my_file.re file */
let foo: int => int = fun x => x + 1;

Parting thoughts

Overall, my experience with Reason has been incredibly smooth. For a change, I love how the compiler acts as someone constantly looking over my shoulder telling me the amazing ways in which I can mess up what I'm doing.

The Reason community on Discord is ultra helpful and has been very patient with my constant newbie questions about anything Ocaml / Reason.

I'm excited to keep tinkering with Reason and eventually build something more serious. In the meantime, I highly suggest you give it a try.

The code for ReKeys is free and open-source on GitHub. Please tweet @_anmonteiro with any questions/feedback about the ReKeys code and/or this blog post!

Happy hacking!