What I Enjoyed — and Suffered from — Writing ReasonML Code as a Junior Engineer

Yuki Li
14 min readMar 6, 2019

This is an article I wrote based on my recent talk at the SF Reason meetup. If you prefer video, you can watch it here: https://www.youtube.com/watch?v=GLYHbhV3row

Who am I, anyway?

I’m a junior engineer who has mostly written JavaScript but also have some experience developing in Python, Java, and Clojure for various projects. More recently, however, I had the chance to ship a product written entirely in ReasonML, including both a native Reason GraphQL server and a ReasonReact client. Here is the story about why I started working on ReasonML project, and what I’ve learned — and suffered through — so far.

Spendbot — the killer social Slackbot

So, Spendbot is the first serious professional project I worked on, and the pitch is pretty simple:

Have you ever struggled with keeping track of where money is being spent by your company?

Do you know what was each of the expense for?

Spendbot knows!

Whenever someone in the company spends money, Spendbot automatically notices and posts the transaction information to Slack so everyone on the team can see the whole company’s expenses.

It’s a great way to effortlessly provide transparency among teams!

​​Spendbot in action — that Google Cloud spend is out of control Sean!

Since I’ve recently shipped the product and customers are happily using it (we get random messages saying “thank you” for it somewhat regularly!), I thought I would take the time now to document the journey building Spendbot, and share some of my thoughts on JavaScript, ReasonML, and surrounding topics. In particular, what are the pros/cons of using ReasonML, and what can junior engineers gain by learning and using ReasonML?

The journey begins: JavaScript

I initially wrote Spendbot entirely in JavaScript. The stack I chose looked something like:

  • Node.js + express for the backend
  • Talking to Postgres for persistent storage
  • A ReactJS front end
  • kue.js + Redis-based queue for workers for any long-running jobs
  • OneGraph for all of the API-related pieces, posting to Slack or fetching transactions

All told, I was able to ship v1 of Spendbot.js to the first customer in roughly a week (a hectic week!).

I shipped my first project that someone would pay for — I was a champion! I pumped my fist in the air as the app went live.

An in-office photo of me shortly after shipping my first product

Sadly, the initial elation didn’t last too long. I was pulled back down to earth when we started getting reports from customers saying that Spendbot was acting in unexpected ways.🤯 Time to debug.

I spent hours with a senior engineer trying to track down the issues (issues like some of the transactions not being posted to Slack, or missing some bits of metadata like the merchant or category). Eventually, we found that my JavaScript code didn’t handle promise-based values properly. In addition to that, I had made quite a few assumptions about the fundamental shape of the data and the scope of the project that turned out to be wrong, and hadn’t asked about them.

What really exacerbated the situation though was that my JavaScript code was written in a way that made it extremely hard to fix the bugs without introducing other bugs elsewhere. I was basically stuck in a situation where in JavaScript,

“There is no safety, except the safety we guarantee ourselves,”

So, to summarize my train of thought as I sat debugging this first version of Spendbot:

I shipped bugs to production.

Our customers saw buggy behavior in the product I built that they’re paying for

The senior engineers had to debug my code in production.

… my life is over.

I’ll tender my resignation tomorrow morning

But luckily, there was a light at the end of the tunnel. Our customer still liked the product despite the bugs! That was a great sign — it meant that now all I really need to do is to make Spendbot production ready.

While planning the steps to make Spendbot production-ready and reflecting on the lessons learned from my first implementation, a senior engineer pitched me on ReasonML. The pitch went something like:

ReasonML is going to hurt. You won’t like it (at least at first).

But it’s the perfect set of training wheels for you right now.

(More honestly, it was less of encouragement, and more of a demand from the senior engineer to use ReasonML for Spendbot v2. I personally had strong doubts about using a different language, and whether it would really help make a project production-ready. Still, I decided to place some faith in the senior engineer’s guidance, since they’re excellent programmers whom I deeply respect.)

So, with Spendbot v2, I took my first steps into the ReasonML world.

My first-month engagement with ReasonML

Let me walk you through the successive phases I went through while learning and writing ReasonML.

As a warning, the initial experience with ReasonML was not very pleasant.

Phase 0: Installation [Difficult level: Annoyance]

The very first step in getting started with ReasonML was the installation. This involved settings up two separate environments, one for the backend Reason-native code, and the other for the ReasonML code targeting the browser and React.

Even though I had a super experienced ReasonML programmer helping me every step along the way, the initial setup took more than half a day! I won’t go into too many details here (largely because I myself am still confused about what exactly we did on that day), but suffice to say that opam, Bucklescript, and emacs were a beast to get to work well together. And honestly, if I had been on my own, there’s no way I would have persisted through it all — it seemed like such a large up-front investment for an unknown payoff at that point.

Still, at the end of the day, it’s (hopefully) just a one-time setup, and it can probably be amortized over many years of happily hacking away with some sophisticated alien tech.

Now with setup out of the way, I could finally get started on the real project!

Phase 1 — Learning About Types and Type Systems [Difficulty Level: +]

The first thing that jumped out at me using ReasonML was that I needed to define types everywhere!? Compared to JavaScript, it felt very cumbersome, and certainly a lot of extra keyboarding!

That said, my first programming language was Java, so I didn’t mind too much. Especially given ReasonML’s type-inference where it seems to be able to determine a lot of the types on its own! After a bit of practice, I was able to get my ReasonML code to look pretty similar to the JavaScript code I had written before, if you squint just right.

I wouldn’t say that the types in ReasonML were really a big challenge.

Phase 2 — Bindings [Difficulty Level: ++]

It turned out that ReasonML on the frontend is strongly intended to interop well with JavaScript libraries, which seems great! I, therefore, tried to use Ant Design, one of the most popular React UI component frameworks.

But the way ReasonML works with JavaScript libraries is through BuckleScript Bindings that we (or someone else, hopefully) write to describe the shape of the library’s API to the ReasonML compiler.

With Spendbot v2’s deadline in a week, and having just spent roughly a day to set up my dev environment and learn the basics of ReasonML’s type system, it was a bit shocking to get hit right out of the gate with a term like BuckleScript Bindings. I’d have to spend at least another day learning to write BS bindings for my UI!

Fine, fine! Just let me push this through! I can do this!

Phase 3 — Everything Else [Difficulty Level: ++++++E]

I kept hustling hard, but problems just continuously popped up, seemingly from nowhere.

When writing Reason native, where is my Js module?

I can’t use the graphql_ppx on Reason native? Why?! How do I deal with all of this JSON?!

So Reason Native and front-end Reason are the same thing, but also very different?

Why don’t any of my editor tools work? I need to remember to switch the opam version before starting emacs for Merlin to work properly?!?

WTF are GADTs in ocaml-graphql-server??!

😱😱😱😱😱😱😱😵😵😵😵😵😵😵😵😵

Some of these concepts might seem straightforward once you learn, but I was very confused at the time. I didn’t even know how to formulate the proper question to describe my problems other than, “Nothing works, and I can’t ship code.”

Why I lost faith in ReasonML in the beginning, especially as a Junior Engineer

As an engineer early in my career, there are three goals I value the most:

  1. To demonstrate my value to the company that took a chance on me
  2. To grow as an engineer
  3. To achieve long-term stability

I disliked ReasonML because it seemed to be a hindrance for me to achieve all the three points every step of the way.

1. Demonstrate value

When I first joined the company for an internship, I was desperate to ship a product quickly, so that I could prove my value. But the steep learning curve of ReasonML slowed me down significantly, and that low productivity could have lasted for a few months.

2. Grow as an engineer

I’m still learning lots of basic things, and I want to be able to explore and experiment with different implementations of ideas and use various libraries. Often times, I need to simply test as quickly as possible if my idea is workable at all.

However, ReasonML often forces me to handle the conditions and states that I simply don’t care about while prototyping. Therefore, the cost of experimentation ends up being very high.

Also, as a junior engineer, pre-built libraries are my best friends. I know ReasonML is still a fairly new community, and we have BuckleScript Binding (which is an excellent tool that allows us to use JavaScript libraries in ReasonML), but learning BuckleScript + learning ReasonML + learning new library/framework makes for an ultra-steep learning curve. It’s a terrible experience, especially when we have tight deadlines.

3. Achieve Long-term stability

Finally, developing skills that can be useful in the future. While it’s true that unlike with JavaScript, in ReasonML I never have to worry about handling the case of 1 + true (which is an intriguing point — philosophically speaking, what would it mean to sum up a number and a boolean🤔), I’m not sure if that alone is enough to make up for the number of companies willing to hire a ReasonML engineer.

I assume that many, many more companies want people who can code in JavaScript compared to the number of companies that want people who know ReasonML. Simply put, I assumed the job market is much richer for JavaScript engineers.

And to top it all off, with the emergence of Typescript and Flow, I was also unsure how many people would appreciate my knowledge of ReasonML.

Despite all my struggles and concerns, I simply didn’t have a choice in the matter, since the tech stack requirements were handed down from above. So, I pushed on, determined. I kept working in ReasonML every day, on the backend and the frontend, for roughly a month.

And surprisingly, things started to change!

A new day dawns: ReasonML’s Benefit Overtake the Pain

At this stage in my career, my instinct was to cut the straightest line to initial success and just make anything function at first. But as I got closer to shipping to production, I grew increasingly nervous.

“I’m developing a real-world product,” I thought, “it should be as smooth and bug-free as possible.”

But in programming, there are just so many things that can go wrong. There can be a lot of edge-cases that I’ve overlooked, sometimes intentionally, but much more often unintentionally.

At this point, I really started to appreciate the superpowers ReasonML gives me.

Oh ReasonML, you really do care about me, don’t you?

ReasonML Superpower 1: Exhaustive pattern matching

While at first glance ReasonML forcing me to handle every trivial condition felt very annoying, with time I learned to structure my code so that there will be fewer cases. And combining that with ReasonML’s exhaustive pattern matching helped me discover so many possible states and edge cases that I had previously overlooked.

For example, what if the user is logged out, but manages to submit a form that should be only available while logged-in? ReasonML forced me to deal with the case that I hadn’t realize might be possible. This alone helped me grow as an engineer and encouraged me to structure my code much more simply, thanks to having a robotic code-pair friend pointing out cases I’ve missed.

ReasonML Superpower 2: Nominal type checking

With ReasonML’s nominal type checking, where the data type is determined by the type’s name, I can be very confident that — at the very least — I’m not passing wildly incorrect data around.

For example, imagine the two following types:

let john : person = {
name: "John",
age: 25,
weight: 130
}
let rover : pet = {
name: "Rover",
age: 63,
kind: "dog"
}

With structural typing, where the type is based on the structure of subfields, it’s possible to confuse a Person with a Pet. In this case, both of them have sufficiently similar sub-structures (both types have fields name and age with the same types), even though they refer to different things. I might have easily passed a Pet when I meant to pass a Person to a function, and that function would happily operate on said dog, thinking it’s a person… and debugging a logic error in human-years vs dog-years would very unpleasant.

ReasonML Superpower 3: Short Feedback loop

I’ve found that as an engineer, I can create all sort of wonderfully weird bugs. I could wait until the testing phase to discover these bugs, but it’s so much better if I have the near-instantaneous feedback provided by ReasonML. Every time I save, ReasonML helps me understand nuances about my coding style and what I’ve done incorrectly, so I can fix bugs much faster at a much earlier stage.

Also importantly, because with this system the time when I create a bug is so close to when I discover said bug, I start to develop muscle memory of how to avoid writing them in the first place.

It’s a kind of automation that helps build competency — what a win-win!

My Favorite ReasonML Superpower: Allowing Easier and Better Communication and Collaboration

Of all these superpowers though, my favorite is that ReasonML makes me feel much more comfortable showing, editing, and talking about my code with other engineers.

I’ve noticed that as my projects scale up, I can hardly keep track of everything I’ve done. In particular, remembering how data is passed between functions and files can be a nightmare.

With Spendbot v1 (written in JavaScript), I became very apprehensive when someone else touched code I’d written because I simply didn’t have a strong grasp on all the potential implications a change could have. But ReasonML knows my data types and how they’re passed around. Whenever I or someone else edit the code, it immediately tells us other things that have broken.

This makes coding collaboration so much easier, whether that‘s refactoring with a senior engineer, or even just me editing the same code three months later. With ReasonML, we can fix the broken parts of our code in the editing phase, and understand quite a bit of the logic of various functions by inspecting the type signature alone.

Reflecting on the needs of a junior engineer, and on recommending ReasonML to junior engineers

Looking back on the initially negative reactions I had regarding ReasonML because of how it affected the three values I held, I realized that the values themselves actually lacked several key points — there were actually some big wins in ReasonML, so how should I update my values to incorporate that?

1. Demonstrate value

I wanted to demonstrate my value by shipping a product quickly. However, what would have more effectively proven my value would be to ship a reliable product quickly. ReasonML provides guidance in covering edge cases, checking data types, simplifying the code, and increasing the readability/maintainability of my code. For a junior engineer, that support greatly decreases the possibility of producing certain kinds of bugs, and even when I do, it’s much easier to identify the source of the problem and then fix it without causing any unintended side-effects.

2. Grow as an engineer

Experimenting with libraries and implementations, and learning new things both help me grow as an engineer. However, communicating my thoughts and revising the code are just as important in accelerating learning and improving coding skills.

The fact that ReasonML enables easier coding collaboration means that a less experienced engineers can receive help from others with much less energy and time required on both sides. Consequently, the learning cycle and feedback will be much shorter and more efficient.

3. Long-term stability

In order to achieve long-term stability as a great engineer, knowing the syntax of individual programming languages seems less important now than developing a strong way of thinking about and constructing code. ReasonML shortens the feedback loop so that I gain a better understanding of the implications of various coding styles. And all that means I notice if my approach seems wrong earlier, and I can respond appropriately.

Closing thoughts, the two important questions:

Would you build a new product with ReasonML today?

Maybe… Yes?

I remain concerned about the lack of resources and the steep learning curve (especially if I am meant to collaborate with non-ReasonML programmers), but the benefits ReasonML provides are very valuable, especially when you think medium-to-long-term.

Would you recommend other junior engineers try to write ReasonML code?

A resounding yes!

Junior engineers can learn many useful concepts, avoid stupid bugs, and start to notice bad habits in their coding patterns by writing ReasonML code. It’s definitely worth trying!

How would you recommend they get started if they’re interested?

One of the most fun ways to do that is by joining a ReasonML Dojo near you! In SF, we have one every month, and have people who have no ReasonML experience write Fruit Ninja or Asteroids in one evening, and have a great time. Come check it! Or, if you are not nearby, here are some resources in the ReasonML community that are helpful, especially the helpful discord server!

Curious for more?

As a follow-up, I wrote a much more light-hearted Spotify DJ App [source] completely in ReasonReact and wrote about my process here, including more product-centric thoughts.

While I’m by no means at the end of my journey, I would like to thank Stepan Parunashvili for so thoroughly reviewing the drafts of this essay, and to thank the ReasonML community (especially on Discord), for helping me along the way so far in learning ReasonML.

📝 Read this story later in Journal.

🗞 Wake up every Sunday morning to the week’s most noteworthy Tech stories, opinions, and news waiting in your inbox: Get the noteworthy newsletter >

--

--