Thinking in ReScript
Tags: rescript, typescript, flow, functional
Published: 2021-08-12 03:37:42

[ReScript](https://rescript-lang.org/) is a new language targeting JavaScript programmers. Particularly JavaScript programmers that have developed an interest in type safety with [TypeScript](https://www.typescriptlang.org/) or [Flow](https://flow.org/).

ReScript feels very familiar due to having a JavaScript-like syntax, however there are some important differences. In this post I'll try provide an overview of some of the key differences that I hope will help you become productive more quickly when exploring ReScript. 

## Everything is an expression

In ReScript, everything is an expression that evaluates to a value, which includes `if` and `switch` statements.

For example, in JavaScript you could write something like:

```js
let x;
if (someCondition) {
  x = 0;
} else {
  x = 1;
}
```

In ReScript this could be written as:

```res
let x = if (someCondition) {
  0
} else {
  1
}
```

## Functions don't need "return"

The last line that executes in a function becomes the return value, so an explicit `return` statement is not needed.

In JavaScript we might write:

```js
const makePoint = (x, y) => {
  return { x: x, y: y };
};
```

And the ReScript equivalent:

```res
type point = {x: int, y: int}

let makePoint = (x, y) => {
  {x: x, y: y}
}
```

## "Function first" thinking

In object oriented programming it's common to see code like this:

```js
const name = person.getName();
```

This uses a *subject - function* pattern. Here `person` is the *subject* and `getName()` is the *function*.

ReScript is a functional programming language which reverses the order to *function - subject*. The example above would be written as:

```res
let name = getName(person)
```

Here's another example which passes some arguments to the function. In JavaScript we might write:

```js
person.setName("First", "Last");
person.setId(1234);
```

In ReScript we would write:

```res
setName(person, "First", "Last")
setId(person, 1234)
```

This is such a common pattern in ReScript that a special syntax exists to support it.

ReScript provides a "pipe first" operator `->` which injects a value into the *first argument of a function*.

The `setName()` and `setId` code above could be re-written as:

```res
person->setName("First", "Last")
person->setId(1234)
```

The pipe first concept so ubiquitous in ReScript that you'll likely see it in almost every ReScript code example.

## Global modules

Each ReScript file is a **module**. There are two important concepts to understand.

First, if you create a ReScript file named `Api.res` and it contains a function `fetchUsers()` then ReScript automatically provides a global `Api` module that you can access from anywhere. To call the function `fetchUsers()` you would write:

```res
Api.fetchUsers()
```

The second important concept to understand is that *your directory structure has no impact on how you access modules*. In other words your `Api.res` file can be anywhere in your project but you would always access it as a global module named `Api`.

A key consequence here is that every ReScript filename in a project must be unique. A common strategy is to use a naming convention to group related modules together, and generally keep your folder structure very shallow.

Here's an example folder/file structure:

```
/api
  /Api_Users.res
  /Api_Products.res
/components
  /Component_Button.res
  /Component_Tile.res
/pages
  /Page_Index.res
  /Page_Products.res
```

## Type naming conventions

Type names in ReScript are always declared starting with a lower case letter, compared to TypeScript or Flow which uses capital letters.

You'll get a ReScript compiler error if you use the incorrect casing.

For example, in TypeScript:

```ts
type Person = {
  id: number;
  name: string;
}
```

And in ReScript:

```res
type person = {
  id: int,
  name: string,
}
```

## The type `t` naming convention

When looking at example code you may frequently see a type declaration named `t`. This is nothing special, it's simply a naming convention referring to the **default** type of a module.

For example:

```res
module Person = {
  type t = {
    id: int,
    name: string,
  }
}
```

So when declaring a person you might write it as:

```res
let person: Person.t = {
  id: 1,
  name: "Laura"
}
```

## Variants

ReScript provides a concept called **Variants**.

These are similar to *enums* but are more powerful because they can also have arguments.

Here's an example:

```res
// Declare a variant type
type choice = Yes | No | Maybe

// Declare a variable of that type, and set its value
let answer: choice = Yes
```

And then later we might use a `switch` statement on that variable:

```res
switch answer {
  | Yes => Js.log("Answer is Yes")
  | No => Js.log("Answer is No")
  | Maybe => Js.log("Answer is Maybe")
}
```

Note some differences in syntax between a ReScript `switch` and a JavsScript `switch`.

Also, `Js.log()` is the equivalent of `console.log()`

This example declares our own variant, but ReScript comes with some useful built-in variants as well.

The most common built-in variant is called `option` which has the two values `None` and `Some`. In this case `Some` is special because it has an argument which we'll show in an example below.

The `option` type is used to represent having a value or not. In JavaScript terms, it's similar in concept to a variable having a value or being undefined.

Let's go though an example of using the `option` type.

First declare some variables:

```res
let nobody = None
let somebody = Some("Mira")
```

Here `nobody` has the value `None` indicating the absence of a value.

And `somebody` has the value `Some("Mira")`. You can think of `Some` as a container holding the value `Mira`.

Next, let's define a function to print these variable values.

```res
let printName = (name: option<string>) => {
  switch name {
    | None => Js.log("Mystery person")
    | Some(name) => Js.log("Person named " ++ name)
  }
}
```

Notice this function takes one argument `name: option<string>`. The type `option<string>` means that `name` will either contain the value `None` or the value `Some` with a string value.

In the `switch` statement we can *unwrap* the `Some` value to get access to its contents using `| Some(name)`. Here the `name` variable is the string `Mira`.

And finally, we can call this function:

```res
printName(nobody)
printName(somebody)
```

Which prints:

```
Mystery person
Person named Mira
```

## Structural Types vs Nominal Types

TypeScript using *structural* types. This just means that it primarily looks at the **shape** of the types.

For example, in TypeScript we might write:

```ts
type Status = 
  | { type: "Idle"; }
  | { type: "Processing"; id: number; }

const value: Status = { type: "Processing", id: 123 };
```

When TypeScript is processing the types it looks at the *shape* of the types. If it contains a `type` property matching "Processing" and a number `id` property then it matches the "Processing" type here.

In ReScript this would be written using *variants*:

```res
type status = 
  | Idle
  | Processing(int)

let value: status = Processing(123)
```

When ReScript is processing the types it uses *nominal* typing which means it looks at the *name* of the types.

Structural typing and nominal typing are different type systems which means that a type solution in TypeScript may require a different approach in ReScript.

## Promises

Promises are such a common concept in modern web applications, it's worth briefly highlighting how the Promise syntax works in ReScript.

Note that here I'm using the [rescript-promise](https://github.com/ryyppy/rescript-promise) library, which will soon become a part of the core language.

The promise functions I'll discuss here are:

* `Promise.then(promise, callback)`
* `Promise.catch(promise, callback)`
* `Promise.resolve()`

Let's assume we have a function already written `Api.getUsers()` that returns a promise.

We could write our ReScript code as follows:

```res
let promise = Api.getUsers()

let promise = Promise.then(promise, users => {
  Js.log(users) // Log the users
  Promise.resolve() // Indicate we are done
})

let promise = Promise.catch(promise, error => {
  Js.log(error) // Log the error
  Promise.resolve() // Indicate we are done
})
```

Notice the "function first" concept in use here; `then` and `catch` accept a promise as the first argument, and also return a promise. We can use the pipe first operator described above to improve this syntax as follows:

```res
let promise =
  Api.getUsers()
  ->Promise.then(users => {
    Js.log(users) // Log the users
    Promise.resolve() // Indicate we are done
  })
  ->Promise.catch(error => {
    Js.log(error) // Log the error
    Promise.resolve() // Indicate we are done
  })
```

## ReScript React

While discussing syntax, a brief mention about using [React in ReScript](https://rescript-lang.org/docs/react/latest/introduction).

React has type safe support in ReScript, including JSX syntax. There are a few syntax differences.

In JavaScript you might write a component like this:

```js
function MyButton({ onClick, children }) {
  return (
    <button
      className="my-button"
      onClick={onClick}>
      {children}
    </button>
  )
}
```

The ReScript equivalent would be:

```res
module MyButton = {
  @react.component
  let make = (
    ~onClick: ReactEvent.Mouse.t => unit,
    ~children: React.element
  ) => {
    <button
      className="my-button"
      onClick={onClick}>
      {children}
    </button>
  }
}
```

The JSX is identical.

The module wrapping the component is just standard boilerplate for React components.

The `make` function is a necessary part of the convention. All ReScript React components have this function. 

## Spend some time learning how to bind to external modules

Lastly, when working in TypeScript and installing an npm module you'll typically look for [type definitions for that module](https://github.com/DefinitelyTyped/DefinitelyTyped).

One of the challenges of the TypeScript community is the effort required to create and maintain the type definitions of these modules.

ReScript encourages a different philosophy. While there are a number of [bindings available](https://rescript-lang.org/packages), and for many packages they will be useful, there is no goal to create a comprehensive repository of bindings. *Instead developers are encouraged to write their own bindings as they need them.*

If you're coming from TypeScript this may seem surprising, but there are important reasons behind this recommendation (which are a bit detailed to go into in this post).

However, writing bindings are easy to learn and in reality take very little time.

For example, writing a binding to the date-fns library `format` function might look like this:

```res
@module("date-fns/format")
external format: (Js.Date.t, string) => string = "default"
```

Breaking this down:

* `@module("date-fns/format")` refers to the module.

* `external format:` indicates it's an external function and names the function `format` within ReScript.

* `(Js.Date.t, string) => string` is the function signature; it takes a Date type and a format string as arguments, and returns a string.

* `= "default"` means use the `default` export from the module.

Once you get used to [ReScript's binding syntax](https://rescript-lang.org/docs/manual/latest/external), it's quick to write and customise those bindings to your specific needs.

## Conclusion

This post highlights some of the key differences I've found between JavaScript and ReScript.

However what I haven't highlighted here is how **similar** much of the syntax and thinking is. Once you feel comfortable with some of these differences above, then writing ReScript code feels just like writing JavaScript but with a slightly different mindset.

Lastly I've found that after spending some time with ReScript it's significantly influence and improved how I write type safe JavaScript.

I hope you found this post useful and that it helps in your exploration of ReScript.