From Reason/React to Rescript/React, Guaranteed Uncurrying 
Tags: reason, rescript, curried, functions
Published: 2021-07-05 19:54:18

---
title: From Reason/React to Rescript/React, Guaranteed Uncurrying 
published: true
description: 
tags: reasonml, rescript, curried,functions
cover_image: https://www.thingsguyana.com/wp-content/uploads/2020/01/chicken-curry-437397-scaled.jpg
---
[image credit](https://www.thingsguyana.com/author/nadira/)

Before we get started, please do ping me if you need a ReasonML/Rescript dev on your team. 

I love reading old ReasonML code. The devs who took the time write ReasonML projects in the early days really had to know what they were doing and it shows. Its an excellent resource for learning both ReasonML and OCaml. For me, the older the code, the better. When I came across this [repo](https://github.com/scottcheng/si-reason) and saw it was four years old, I could not resist. On top of that, its a wonderful little piece of code. 

Today's old repo is [si-reason](https://github.com/scottcheng/si-reason) by [@scottcheng](https://twitter.com/scottcheng) who was dabbling in ReasonML 4 years ago when the compiler was on `bs-platform@2.0.0`. Except for his React version, his code ran today on Rescript and ReasonML latest. You can see what he built at https://scottcheng.github.io/si-reason/.

As I'm studying this old reason code, I do some clean up, update to latest ReasonML and get it working. Then I decide to switch the syntax to Rescript since that is the new thing and I come across this one line that doesn't work after converting it from ReasonML to Rescript. 

## Generated Rescript Version

```rescript
  let (state, dispatch) = React.useReducer((_, action) =>
    switch action {
    | UpdateColumnPositions => {
        columnPositions: emptyColumnPositions 
        |> Array.mapi((x, row) =>
          row |> Array.mapi((y, _) => {
            let rect = ReactDOM.domElementToObj(
              getElementById(BoardBase.markerId(x, y)),
            )["getBoundingClientRect"]()
            (rect["left"], rect["top"])
          })
        ),
      }
    }
  , {columnPositions: emptyColumnPositions})
```

converts to

```javascript
  var match = React.useReducer((function (param, action) {
          return {
                  columnPositions: $$Array.mapi((function (x, row) {
                          return $$Array.mapi((function (y, param) {
                                        var rect = document.getElementById(BoardBase$SiReason.markerId(x, y)).getBoundingClientRect();
                                        return [
                                                rect.left,
                                                rect.top
                                              ];
                                      }), row);
                        }), emptyColumnPositions)
                };
        }), {
        columnPositions: emptyColumnPositions
      });
```
## Original ReasonML Version

The same code in ReasonML:

```ocaml
    React.useReducer(
      (_, action) =>
        switch (action) {
        | UpdateColumnPositions => {
            columnPositions:
              emptyColumnPositions
              |> Array.mapi((x, row) =>
                   row
                   |> Array.mapi((y, _) => {
                        let rect =
                          ReactDOM.domElementToObj(
                            getElementById(BoardBase.markerId(x, y)),
                          )##getBoundingClientRect();
                        (rect##left, rect##top);
                      })
                 ),
          }
        },
      {columnPositions: emptyColumnPositions},
    );
```

converts to:

```javascript
  var match = React.useReducer((function (param, action) {
          return {
                  columnPositions: $$Array.mapi((function (x, row) {
                          return $$Array.mapi((function (y, param) {
                                        var rect = document.getElementById(BoardBase$SiReason.markerId(x, y)).getBoundingClientRect();
                                        return [
                                                rect.left,
                                                rect.top
                                              ];
                                      }), row);
                        }), emptyColumnPositions)
                };
        }), {
        columnPositions: emptyColumnPositions
      });
```
That is the code produced by the Rescript compiler. One works, one does not. 

If you can't see the difference, let me help you out. Here is a screenshot of the diff.

![image of git diff showing the files are the same](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v9ghse8bbn5gikayeoik.png)

The files are exactly the same.

I produced this code by runnning `npx rescript convert -all` which is documented here:

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">$ npx rescript convert -all 🔥</p>&mdash; ReScript (@rescriptlang) <a href="https://twitter.com/rescriptlang/status/1410150486515359747?ref_src=twsrc%5Etfw">June 30, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

This is an excellent script which up until this example, always worked when converting from Reason to Rescript. The conversion is one way though so if you like your Reason code, like I do, be aware of that.

Back to the point. The code that fails was the Rescript code. If you look at the two versions of Javascript, you will notice that they are exactly the same. What happened? 

Per the Rescript docs:

> In a dynamic language such as JS, currying would be dangerous, since accidentally forgetting to pass an argument doesn't error at compile time

[source](https://rescript-lang.org/docs/manual/latest/bind-to-js-function#curry--uncurry)

So the Rescript docs are telling us that we might expect that using Rescript, your code might compile but then throw an error when its run.

So is the ReasonML code safer in that regard? I image that the above statement can be made about ReasonML as well but that for some reason, the ReasonML code happens to produce `correct` Javascript while the Rescript compiler does not. Anyone with an idea on why this is happening, please do share. I would love some feedback on it.

In ReasonML, all functions are curried by default. I could tell you about it but [@glennsl](https://twitter.com/glennsl) knows something about OCaml and ReasonML and explains it well here:

{% stackoverflow 53644093 %}

Or let Axel Rauschmayer, [@rauschma](https://twitter.com/rauschma) tell you about on the seminal [2ality](https://2ality.com/2017/12/functions-reasonml.html#currying-(advanced)) blog and twitter feed [@2ality](https://twitter.com/2ality);

The fix for the Rescript code is easy is enough. Once you realize that the code might need to be curried by reading this post or through years of experience, well then you can curry the Rescript version and see what happens. You do that as follows: 


```rescript
      let rect = ReactDOM.domElementToObj(
              getElementById(BoardBase.markerId(x, y)),
            )["getBoundingClientRect"](.)
            (rect["left"], rect["top"])
```

Note the `(.)` after `getBoundingClientRect`. That is how you uncurry a function in both ReasonML and Rescript though the ReasonML version does not require it. It happens automatically. I guess this means that Rescript is `mostly` curried by default. Again, if you need to `uncurry` a function you need to add the dot as shown. 

This kind of silent break down is exactly the reason why I took up ReasonML. I could not take chasing down another silent error in Javascript code.

The Rescript team is aware of this and have a section in the documentation addressing it. See [Use Guaranteed Uncurrying](https://rescript-lang.org/docs/manual/latest/bind-to-js-function#solution-use-guaranteed-uncurrying) in the docs.

Thanks for teaching [@scottcheng](https://twitter.com/scottcheng), [@rauschma](https://twitter.com/rauschma) and [@glennsl](https://twitter.com/glennsl).

Having just converted and updated the existing repo, I'm thinking that maybe we can avoid this altogether by structuring the function differently. I'll try to do that and update this post.

## Show me the code

[working reason code](https://github.com/idkjs/si-reason)
[good rescript code](https://github.com/idkjs/si-reason/tree/rescript-good)
[bug reproduction](https://github.com/idkjs/si-reason/tree/rescript-repro)

If you are into this sort of thing, here is a script I use to do a lot of the clean up.

{% gist https://gist.github.com/idkjs/b1a9e65c8c3759918f495af7bdeab658 %}