First day with ReasonML

Long time I craved for something like ReasonML. It allows me to keep using the JS environment of NodeJS I highly adore, while converting crucial and complex code to a statically typed, and typed sound language.

On top of that, the immutable constructs coming with the language is a huge bonus for my specific needs. Especially given they do not add any performance overhead after compilation. (Been burned by immutable.js before).

But going deep into reasoning I’ll keep for another post, after some time hearing about ReasonML today I decided to give it a run. Play with it directly on the code I want to eventually use it in, and bumping my head into many wall. Dreams aside, getting real.

As you can understand, I’m working with existing JS code, specifically code for the server side in NodeJS. Lucky for me, this code is pure business logic and have no external dependencies other than moment.js , and I have decided to start with pure modules with no dependencies at all.

I set up the ReasonML environment, not without issues at all.

First, the global installation didn’t worked at all due to permission issues on linux. When installing globally npm packages, I run sudo npm install .. , since when installing globally npm is writing to locations that require sudo permissions. But the Reason installation have a script that needs to run, but it runs it as a regular user, which does not have access to the files where the script is located. It required to run npm install with disabling the script running and then running the script manually after chmoding it into submission.

Then, the documentation doesn’t explain how to add Reason into existing project, just how to install globally and initial a new project through the CLI. So I had to initiate a new project and work backward from there what is needed for an existing project. Not too bad, but not convenient as well.

Alright! now we can start coding!

I decided to take a small utility function from my code and convert it to Reason. The first challenge I had was integrating with the JS Object already in use in the code. And boy, this turned out to be harder than I thought.

I started by reading about Objects in the Reason docs: https://reasonml.github.io/docs/en/object.html
Which quickly sent me to bucklescript docs for implementing JS objects:
https://bucklescript.github.io/docs/en/object.html#object-as-record

Alright, fair enough, let’s implement it. I created types for my object, and wrote a function, which should be pure. Just updating some values, in JS it was looking like:

Basic standard stuff. So I expected my Reason code to look the same. Coming to use the spread operator on the Reason JS Object syntax, I got a syntax error. It simply does not support it, and I would have to user JS Object.assign() within Reason Context to do that.

But wait, I saw all the fancy ReactReason example code! they do user the spread operator! Right, because there they use a native Reason record . So they get all the goodies, like cleaner syntax, usage of native Option and, real immutability, which is one of the reasons I went for Reason in the first place.

Ok, I’m only playing so far. I can move to native record and see how it goes.

So I converted to native record, and I got my fancy spread operator, and cleaner syntax. Hooray!
Just, the code now is completely incorrect.. When using records, bucklescript no longer have to keep JS Object structure, so now all the data is within arrays. Not good.

But there are generated converters just for this apparently.
https://bucklescript.github.io/docs/en/generate-converters-accessors.html#convert-between-jst-object-and-record

Using them, we are back to normal, operational code.

Now let’s tell Reason the simple record I used so far, actually is not so simple, and has other keys in it. It has some dynamic keys too. Which are not an issue in js, simply do myObj[dynamicName + "wow"] . But in Reason, records keys are completely static. Ah oh.

I started reading all over the place, and got to this Dr. Axel post: http://2ality.com/2018/01/records-reasonml.html#is-there-a-way-to-dynamically-specify-the-name-of-a-field

It basically says, if you need it dynamic, do not use records.

But I remembered using JS.Dict in my initial tries.. mm.. will it work within a record?
I have to note, this is how my JS object looks like:

I decided to create a Js.Dict.t within my record, for the dynamic key, that will then have a record type for values, as the values are static and know.
This resulted with:

And it worked. To my surprise.

Now I had to write the function that goes through all those dynamic keys values, and update them. Using map was of course the reasonable thing to do.
So I used Js.Dict.map to map through the values of that type. It didn’t work.

I got this error message:

I started diving into the issue, that ended up with me trying the underlying code for map from bucklescript implementation, and finding that removing [@bs] annotation before the function, fixed it.

Asking around in the amazing Reason discord, they explained this function expects a JS object type, and I used a record for values of the Js.Dict. So changing the map function by adding a dot before the value, coerced it into bucklescript JS object. from let f = (v) to let f = (. v) .

Conclusion

Many more things had happened, but I think I wrote enough already.

I plan to take a look into Belt , bucklescript new standard library, to see if it might fit me better in some ways.

I think ReasonML have good potential. It lacks in documentation, but it is understandable at this phase. It’s game is strong with ReasonReact, which is to be expected as the biggest real world application right now for Reason are with react.

I haven’t made up my mind on whether I should use ReasonML in production. Mostly due to difficulties with integration. But the experience so far, with the type system, and with the nice errors was positive. So I’ll keep on trying on my spare time (which is code name for working at nights).

Programming enthusiast. https://www.snir.dev