How I Switched from TypeScript to ReScript
Tags: typescript, javascript, react, rescript
Published: 2021-01-20 19:59:02

_A glimpse into a more civilized (yet challenging) tool in the JavaScript ecosystem_

_Article originally published at [Medium](https://medium.com/att-israel/how-i-switched-from-typescript-to-rescript-637aa5ef8d3)_

This is not evangelism of ReScript or a one-to-one comparison with TypeScript. I love TypeScript. I decided to rewrite a small TypeScript+React+Jest side project into ReScript.

ReScript is not new. In a way it’s as old as JavaScript itself. ReScript is a rebranding of ReasonML (_Facebook_) and BuckleScript (_Bloomberg_), which wrap [OCaml](https://ocaml.org/) on both ends. The former is an interface of the OCaml syntax, while the latter makes sure to compile the AST into JavaScript. ReasonML was created by Jordan Walke, the creator of React. [ReasonML](https://reasonml.github.io/) still exists as a parallel project to ReScript, with a slightly different syntax and mission.

![Image for post](https://miro.medium.com/max/1156/1*yRmSBblfxfX81txBJYTcEg.png)

*ReScript syntax compiling into OCaml Abstract-Syntax-Tree, and BuckleScript compiling into readable, optimized JavaScript*

ReScript is not just a rebranding: it’s a ReasonML which freed itself of the yoke of the OCaml ecosystem. By doing so, it forfeited compilation to native code and OCaml library interop, but gained a freer syntax which further resembles JavaScript to embrace its developers, eager for better tools.

# First Impression

My first attempt was to just install ReScript on my project, start the watcher, rename an easy file into `.res` and be guided by the errors. I immediately learned that refactoring into ReScript is not “breadth-first” but “depth-first.” Simply renaming the file extension won’t work, as the compiler stops completely at type errors.

In TypeScript one can gradually assign types and interfaces to dynamic types, while tagging some as `unknown` or `any`. Depth-first means that you start with one small function, or one small React component, and write it properly. If all the types are right — and with mathematical precision— your code will compile into JavaScript.

While TypeScript often transpiles into unreadable code, it’s good practice to keep an open tab on the auto-generated `js` file from ReScript. You’ll be pleasantly surprised by the speed of transpilation, the conciseness and readability of the code, and the performance of such code. If the ReScript code compiled, it means its types are safe and sound, so it can optimize away all the noise.

The only exception I saw to readability and performance of the generated JavaScript was in curried functions. All functions in ReScript are curried by default, and some of them generate code which imports a Currying library. This didn’t happen often, and currying can be disabled.

But what about TypeScript? Inter-operation with JavaScript code is trivial, but importing and exporting types from TypeScript (or Flow) can be more complex, and it creates two sources of truth: one for ReScript types and another for TypeScript.

GenType, described below, auto-generates a typed `tsx` file from your ReScript code which you can import into other modules. This helped for exporting ReScript types, but it’s not possible to import TypeScript ones. The automation of type conversions [eased the problem of the two sources of truth.](https://www.javierchavarri.com/adopting-reason-strategies-dual-sources-of-truth-and-why-gentype-is-a-big-deal/)

Furthermore, the generated ts code uses CommonJs `require` syntax, which break when using native ECMAScript module support. I also had to tweak my `tsc` to not transpile the auto-generated `tsx` into a fourth (!) source file:

-   `.res` ReScript source code.
-   `.bs.js` compiled JavaScript, which you can ignore in your source control
-   `.gen.tsx` auto-generated by GenType, which import the compiled JavaScript code and re-export it with proper types. Also add to your `.gitignore`.
-   `.gen.jsx` accidentally transpiled by TypeScript, delete it and reconfigure your `tsconfig.json`.

I first rewrote my algorithms, since they didn’t have any third-party imports to inter-operate with, and the import syntax was daunting for me at first. Some teams go for a [data-first strategy, or a UI-first one](https://www.javierchavarri.com/adopting-reason-strategies-dual-sources-of-truth-and-why-gentype-is-a-big-deal/) (as Facebook did in 2017 for Messenger.com, [rewriting 50% of the codebase](https://reasonml.github.io/blog/2017/09/08/messenger-50-reason.html)).

# Types

ReScript is part of the **statically typed functional programming language family**, which means it’s not compiling. Just kidding, it means it uses the Hindley-Milner type algorithm, which deduces types with 100% certainty and can prove it mathematically as long as your variables are immutable (and a few other language design choices). TypeScript on the other hand tries to do it’s best at finding a common type for all your usages.

This might blow your mind as a TypeScript user, but the following ReScript function is fully statically typed:
```
let add = (a, b) => a + b
```
ReScript knows with provable certainty that `a` and `b` are both `int` and that the function returns an `int`. This is because the `+` operator only works on two `int` and returns an `int` . To concatenate two strings you’d use `++` and for two floats use `+.`. To combine two different types you need to convert either of them. Also, no semicolons.

If you’re like me and like to type your code as you prototype, you can do so as you’d expect:
```
let add = (a: int, b: int): int => a + b
```
The generated JavaScript code in both cases is the same (ReScript v8.4.2):
```
'use strict';
function add(a, b) {  
    return a + b | 0;  
}
exports.add = add;
```
Notice how I didn’t specify any module exports but the resulting code did. This shows how everything in the module/file is exported by default. The JavaScript function itself is not type safe, so importing it in a JavaScript module and using it there won’t have all the advantages of ReScript.

You can try it for yourself in the official [playground](https://rescript-lang.org/try?code=DYUwLgBAhgJjEF4IAooBoICMCUiB80EA1FkA).

# Generating TypeScript

To interoperate with TypeScript with proper type information you’ll use third-party [genType](https://github.com/reason-association/genType). Add it as a devDependency and annotate the module export you want to generate with `@genType` (in previous versions you’d surround annotations with square brackets).

```
// MyModule.res
@genType  
let add = (a,b) => a + b
```

This will result in the following TypeScript. Notice how the generated TypeScript imports the generated JavaScript `MyModule.bs.js` file:
```
// MyModule.gen.tsx
const MyModuleBS = require('./MyModule.bs');
export const add: (_1:number, _2:number) => number = MyModuleBS.add;
```
GenType generates a one-line re-export of your generated `.bs.js` file, with proper TypeScript typing. From this example you’ll notice two more things:

-   Every file is a module.
-   Everything is exported.

Here’s an [example](https://github.com/reason-association/genType/tree/master/examples/typescript-react-example) repo genTyping to TypeScript with React.

For using TypeScript types, see “Importing TypeScript Types” below.

# Records

There is only one type which does need a type declaration, which is the [record](https://rescript-lang.org/docs/manual/latest/record) type. A type declaration will look like this and produces no JavaScript code:
```
type student = {  
  age: int,  
  name: string  
}
```
Types must begin with a lowercase! If we prepend it with `@genType`, the generated TypeScript will look like this:

```
// tslint:disable-next-line:interface-over-type-literal_  
export type student = {  
    readonly age: number;  
    readonly name: string  
};
```
If you’re wincing at the lower-cased type breaking all your conventions you can rename the type on conversion with `@genType.as("Student")`. This will add another line of code below the previous one:
```
export type Student = student;
```
Also it includes a `tslint` ignore line, which I hope they switch soon to `eslint` as the former is deprecated.

These are record types, not ReScript [objects](https://rescript-lang.org/docs/manual/latest/object) (don’t misuse the `string` type on them). As soon you type something like `foo.age` ReScript will know that `foo` is of type `student`. In case there’s another record with and `age` field, it will infer it’s the last one declared. In that case you might want to explicitly annotate the type.

In the case you don’t want that much ceremony, you can use the `object` type and index it with a string: `student["age"]`; then you don’t need to declare a type.

Furthermore you can use `student` as a variable name, so `student.age` is a valid expression, TypeScript would scream at something like this. Variables (that is, bindings) and Types live in a separate namespace, so a `student` of type `student` an be written as `student: student`.

# Nominal Typing

Record types have “_nominal typing_” similar to Java or C#, as opposed to TypeScript’s “_structural typing._” This is why interfaces are so important in TypeScript, and are used much more than Types. TypeScript doesn’t really care about “_what you are_”, it cares about “_how you look_.”

For instance, if there’s another type, say, `teacher` with the same fields of a `student`, you cannot assign a `student` to somewhere expecting a `teacher`:
```
// defined first  
type student = {  
  age: int,  
  name: string  
}

// defined last  
type teacher = {  
    age: int,  
    name: string  
}

// t is a teacher  
let t = {  
    age: 35,  
    name: "Ronen"  
}

let s: student = t // Error!
```
You’d get a colored error saying:
```
We've found a bug for you!
//...This has type: teacher
Somewhere wanted: student  
FAILED: cannot make progress due to previous errors.  
>>>> Finish compiling(exit: 1)
```
Unlike TypeScript’s `tsc` compiler, `bsb` won’t begrudgingly continue its transpilation work into working JavaScript. It will stop with a non-zero exit code, and you have to fix the issue in order to make any progress.

# Optionals

One of the features I most like in modern TypeScript (or future JavaScript) are the optionals. They make working with nullable types easy and concise:

`const something: string = foo?.bar?.baz ?? "default";`

`something` will be the content of `baz` if it reached that far, or be `"default"`.

There are no `null` or `undefined` in ReScript. But we can work with nullable values using the Variant [option](https://rescript-lang.org/docs/manual/latest/null-undefined-option). But how can we get the elegance of the above TypeScript code? I tried to answer this question but, we can’t, currently. Not enough sugar.

As with other functional languages, we can use a myriad of interesting library functions. Some of [Belt utility functions](https://rescript-lang.org/docs/manual/latest/api/belt) are:

-   `Belt.Option.Map` will execute a function on the optional value if it exists, or return `None`.
-   `Belt.Option.getWithDefault` will return a default if the optional is `None`.
-   `Belt.Array.keepMap` will trim away all `None` values from an array.

But for this case, the best option is with [Pattern Matching](https://rescript-lang.org/docs/manual/latest/pattern-matching-destructuring):
```
let baz = switch foo {  
   | Some({ bar: Some({ baz: baz })}) => baz  
   | None => None  
}
```
There isn’t yet a sugared syntax for optionals; the optional operators are very new to TypeScript as well.

The important quality of pattern matching is that the compiler will complain if there’s any case — doesn’t matter how deeply nested — you haven’t addressed. It’s best practice for most cases.

# Pipes

Pipes are great. They compile this code:
```
person  
  ->parseData  
  ->getAge  
  ->validateAge
```
Into this:
```
validateAge(getAge(parseData(person)));
```
Previous versions used a triangle operator `|>`. The difference is in where to shove the data: as the first parameter, as the arrow does, or as the last parameter, as the deprecated triangle does. [More about this.](https://www.javierchavarri.com/data-first-and-data-last-a-comparison/)

Notice that in the case of a one-parameter function we don’t write the `unit`, that is `()`. This is a common beginner’s mistake. In the case of multiple parameters, the value gets passed as the first one and the other parameters begin with the second one.

This is especially important in a functional language, since we lose some of the elegance of calling methods in objects.

What would be a JavaScript method call such as map:
```
myArray.map(value => console.log(value));
```
Has to be written functionally in ReScript as:
```
Belt.Array.map(myArray, value => Js.log(value))
```
But can be rewritten as:
```
myArray -> Belt.Array.map(value => Js.log(value))
```
As a newcomer I try to find a use for it anywhere I can, which can lead to the bad practice of rewriting code around it to impress my coworkers. To use it on JavaScript libraries you’ll have to [write the correct bindings](https://rescript-lang.org/docs/manual/latest/pipe#js-method-chaining) for them. This is one thing I’d like to see in JavaScript. [Here are a few stage-1 proposal](https://github.com/tc39/proposal-pipeline-operator/wiki)s.

By the way, if you’re not using [Fira Code](https://github.com/tonsky/FiraCode) then you’re missing out on a lot of the pipe’s aesthetics.

# Promises

This was very frustrating for me. I love using modern `async` and `await` syntax in my code, which ReScript didn’t implement yet. I had to go back into thinking about `then` and `resolve`, which made simple code look complex.

The following code:
```
const getName = async (id: number): Promise<string> => {  
    const user = await fetchUser(id);  
    return user.name;  
}
```
Is de-sugared into:
```
const getName = async (id: number): Promise<string> =>   
    fetchUser(id).then(user => user.name);
```
Now consider `then` to be a function in the `Js.Promises` module instead of a method, which accepts `fetchUser(id)` as its last parameter, and you can write it like this:
```
let getName = (id) =>  
    Js.Promise.then_(  
        user => Js.Promise.resolve(user.name),  
        fetchUser(id))
```
Typed as `Js.Promise.t<string>`, and with arrow pipe syntax for readability, the above function can be written as:
```
let getName = (id): Js.Promise.t<string> =>  
    fetchUser(id) |> Js.Promise.then_(  
        user => Js.Promise.resolve(user.name))
```
The Promise library still uses the old convention of passing the data as the last argument, so in order to use the newer arrow pipe, an underscore has to be placed in the proper location.

Here are [examples](https://gist.github.com/lilactown/a8d1dc6aa2043efa62b23e559291053e) for Promises written in the (almost-identical) ReasonML syntax.

The ReScript team [promised](https://rescript-lang.org/docs/manual/latest/promise) (no pun intended) to implement a Promise API revamp with their own `async` and `await`.

# Import JavaScript Modules

If you’re writing only in ReScript you don’t need to bother with imports or exports, and this is done under the hood. Every file is a module and everything in it is exported. If you only want specific things exported you do so with an interface file. To import JavaScript modules however, the syntax can get complicated.

To import `dirname` from the `path` module, you’d write:

@bs.module("path") external **dirname**: string => string = "dirname"


![Image for post](https://miro.medium.com/max/2402/1*ThMyXVEUdIQOQ5LGARvwHg.png)

the elements of an import from JavaScript files

Then use it accordingly:
```
let root = dirname("/User/github") // returns "User"
```
For ReasonReact this became particularly tiresome, as I had to define inline modules for each React Component, and reexport the default export as the “make” function, paying attention to named parameters such as “children.” Here I imported the Container from [react-bootstrap](https://react-bootstrap.github.io/) and used it in ReasonReact:
```
module Container = {  
    @bs.module("react-bootstrap/Container")  
    @react.component  
    external make: (~children: React.element) => React.element = "default"  
}
@react.component  
let make = () => <Container> ...
```
# Redex

![REDEX: Reason Package Index](https://miro.medium.com/max/2736/1*cFbCBs3cNQnvsGsh12Nlpg.png)

For this case I can get the bindings from [redex,](https://redex.github.io/) and add it as a dependency both to my `package.json` and my `bsconfig.json`. I can then import it with `open ReactBootstrap` at the top of my file. This is similar to [DefinitelyTyped,](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwjc45OLh5TuAhWEsHEKHRnVBsYQFjABegQIARAC&url=https%3A%2F%2Fdefinitelytyped.org%2F&usg=AOvVaw0b68ZVMiJ_WzZ7tP-Yh8p0) where you can find high-quality type definitions for TypeScript.

For this case however I ran into an error, as the package I needed was not updated to the latest version. I had to fork it and [manually update](https://dev.to/erykpiast/reasonreact-migration-guide-to-jsx-3-and-hooks-37op) it to `react-jsx` version 3.

# Importing TypeScript Types

You can’t import a type from TypeScript and use it in ReScript, you have to re-declare it. However, you can link the type you created to the original TypeScript one for correct inter-operation. Here’s an example with Node.js’ `fs` module:
```
@genType.import(("fs", "Dirent"))  
type dirent
```
Notice that I passed a `tuple` to `import`, not an argument list. This will link my type `dirent` to `fs.Dirent`, and will generate the following TypeScript:
```
import {Dirent as $$dirent} from 'fs';_// tslint:disable-next-line:interface-over-type-literal_  
export type dirent = $$dirent;
```
You can declare the entire type, in case you need to use its properties, or leave it as is.

Because of the syntax overhead of TypeScript-ReScript inter-operation, I recommend doing it as little as possible, using each language in separate areas of your app.

# ReasonReact

![Image for post](https://miro.medium.com/max/1376/1*7i6JxB1WnC6d7kGojw98NQ.png)

ReasonML (now ReScript) was created by Jordan Walke, the creator of React. Reason+React pushes the React philosophy further by utilizing the language syntax and features for ReactJS’s programming patterns.

ReasonReact provides smooth JS interop and uses built-in language features to integrate into UI framework patterns left unaddressed by ReactJS, such as routing and data management. Using them feels like “_just using Reason._”

The documentation for ReasonReact still uses the old syntax, so things like:
```
[@react.component]
```
Needs to be changed into:
```
@react.component
```
If you want to use the old syntax, just change the file extension to `.re` instead of `.res`.

ReasonReact is stricter than ReactJS, mainly in its use of types (e.g., strings need to be used with `React.string()`in JSX. Other than this, the `React.useState` returns a proper `tuple` instead of an array, the way it was originally intended. Finally, React Components are rendered through a `make` function, and prepended with `@react.component` (I added `@genType` as well for TypeScript generation):

For the example, I imported this component into a React TypeScript file:
```
// index.tsx
import { make as Demo } from "./pages/Demo.gen";
// ...<Demo name={"Foo"} />
```
Which, when rendered, looks like this:

![Image for post](https://miro.medium.com/max/420/1*x7eXd21SMWeESmc9GHugjw.gif)

In case we don’t want GenType for TypeScript generation, we just import `Demo.bs` instead.

# Testing

In order to write tests in ReScript, and thus test your code directly, you can use [bs-jest,](https://github.com/glennsl/bs-jest) which provides ReScript bindings to Jest. If you prefer, you can also use the slightly less mature [bs-mocha](https://github.com/reasonml-community/bs-mocha). You can also test the generated JavaScript or TypeScript files with no extra configuration.

Since ReScript is in the JavaScript ecosystem it makes little sense to create specialized testing tools for ReScript, and the direction seems to be in developing bindings for JavaScript testing tools.

With bs-jest, you have to name you can’t name your file `foo.spec.res`, only with a valid module name, such as `foo_spec.res`. Jest will run on the compiled folder, by default inside `lib/js`. Also, assertions are not executed immediately, but instead returned by the function and run at the end of the suite. It’s a functional way to thing about tests. Consequently, you can only write one assertion per test, which is best practice anyway.

# Tooling

ReScript devs did well on prioritizing the [plugin for VSCode,](https://github.com/rescript-lang/rescript-vscode) which works really well. With the ReScript’s watcher running, you’ll see your Type errors underlined in red, with a descriptive bubble on hover. You also get type hints, formatting, and jumps to definitions. There’s also official support for [Vim](https://github.com/rescript-lang/vim-rescript) (both plain Vim and Coc Language Server) and [Sublime](https://github.com/rescript-lang/rescript-sublime).

![Image for post](https://miro.medium.com/max/1200/1*SpbTRW8aFP85FlHbC3SIEQ.gif)

Screen capture from [rescript-vscode](https://marketplace.visualstudio.com/items?itemName=chenglou92.rescript-vscode).

# The Community

A few times in my coding career I had to work with small communities, and I always loved it. I developed smart-contracts in [Solidity](https://en.wikipedia.org/wiki/Solidity), some database queries in the functional language [Q,](https://en.wikipedia.org/wiki/Q_(programming_language_from_Kx_Systems)) and Roku channels in [BrightScript](https://developer.roku.com/docs/references/brightscript/language/brightscript-language-reference.md). You end up working with Slack/Discord/Gitter open and code together with the few others going through your similar problems. You don’t even bother checking StackOverflow for answers.

This forces you to read and reread the official documentation and examples, as you don’t want to look like dumb in the chatroom. Also, you’re part of a community maintained by real people, where you can always contribute something interesting, and even shape its development.

Not all communities are alike, of course. I personally found the ReasonML/ReScript community to be welcoming. ReScript has an [official forum](https://forum.rescript-lang.org/) where you can communicate asynchronously and with a permanent paper record you can search. The core team consists of a [handful of developers](https://forum.rescript-lang.org/) with public Twitter accounts, and there’s an [official blog](https://rescript-lang.org/blog). I found however that the community hangs around in the [ReasonML’s Discord server](https://discord.gg/mVY4NBdRvU), in an unofficial ReScript room.

Finally, there’s [ReasonTown](https://anchor.fm/reason-town), “a podcast about the ReasonML language and the community that makes it good,” [ReasonConf](https://www.youtube.com/channel/UCtFP_Hn5nIbZY4Xi47qfHhw/videos)’s YouTube channel, and [Redex](https://redex.github.io/), to find bindings for your libraries.

# Conclusion

The switch is not easy; a refactor of an existing app is even more difficult given its fatal stop on the first issue. This will certainly hinder its adoption. Popular transpilers, such as TypeScript, SCSS, or CoffeeScript garnered adoption by its ease. Just copy-paste your code — or rename your file — and you’re done.

This is different. ReScript, as with other statically typed functional languages, aims at changing the way code is approached at a fundamental level. I believe we’ll see a greater adoption of functional programming in the future, eventually becoming the default for some industries. This is due to the mathematical approach to types, formal verification of a program’s correctness, and given immutability: less moving pieces and mental mapping.

We’re already at the first stage of adopting a “functional style” in the ecosystem with `map`, `filter`, `reduce` functions in JavaScript. ReScript represent the next — hybrid stage — of a properly functional language from the ML family which compiles to the industry’s standard JavaScript.

Functional programming at its core takes itself seriously. It’s mathematical, formal, and doesn’t comply with hacks. It aspires to deals with truths, not processes. Writing a «functional style» in JavaScript only whets one’s appetite for more, as the language brings one’s good intentions down, not up. ReScript, while frustrating, might be the precision tool for a more civilized future in the ecosystem.