Type Annotation in ReasonML
Tags: reason, types, annotation
Published: 2019-12-22 02:20:17

One of the most powerful features of ReasonML is its type inference system. In code like this:

```reasonml
let age = 42;
let price = 10.66;
let word = "reason";
let isValid = true;
let hours = [10, 2, 4];
let focalLength = (objDist, imgDist) => {
  (objDist *. imgDist) /. (objDist +. imgDist);
};
```

ReasonML figures out the type of each of these bindings. If you’re using an editor like Visual Studio Code with the [reason-vscode extension](https://marketplace.visualstudio.com/items?itemName=jaredly.reason-vscode), you can see what ReasonML has inferred:

![image showing int, float, string, bool, list(int), and function types for the bindings](https://thepracticaldev.s3.amazonaws.com/i/pdw093ljvd9egib185dq.png)

The type inference system does such a good job of figuring out what types you’re using that you can write an entire program without having to specify any types. But type inference is still there, looking over your shoulder and letting you know if you do something wrong, as in line two:

```
  We've found a bug for you!
  /home/david/annotation/src/Demo.re 2:19-21

  1 │ let age = 42;
  2 │ let total = age + "3";
  3 │ let price = 10.66;
  4 │ let word = "reason";

  This has type:
    string
  But somewhere wanted:
    int
```

Sometimes, though, there are situations where the type inference system can’t figure out what you need. Sometimes there’s an ambiguous situation (two different record types with the same fields or similar ones in different modules) where type inference makes a choice&mdash;but not the one you want. And sometimes you just plain *want* to do your own type annotation.  Here’s how to do it.

# Annotating Value Bindings

For bindings to a value, you follow the variable name with a colon and the value’s type. Here are our original value bindings with explicit annotation:

```reasonml
let age: int = 42;
let price: float = 10.66;
let word: string = "reason";
let isValid: bool = true;
let hours: list(int) = [10, 2, 4];
```

While you can annotate value bindings, almost nobody does this. In most cases, the expression on the right-hand side makes the type sufficiently clear that adding the annotation won’t give you an exponential gain in clarity.

However, many people *do* annotate function bindings.

# Annotating Function Bindings (Method 1)

Function binding annotation follows the same pattern, with the type information between the function name and the equal sign:

* Start with a colon
* In parentheses, specify the parameter types
* Add `=>`
* Specify the return type

Here’s the annotation for the focal length function:

```reasonml
let focalLength: (float, float) => float  =
  (objDist, imgDist) => {
    (objDist *. imgDist) /. (objDist +. imgDist);
  };  
```

# Annotating Function Bindings (Method 2)

The preceding method with the type information separate from the parameter list and function body is familiar to people coming from a language like Haskell or Elm.

If you’re coming from a language like Java or TypeScript or Flow, you’re used to seeing type information attached each individual parameter. ReasonML supports that kind of notation as well:

```reasonml
let focalLength = (objDist: float, imgDist: float): float => {
    (objDist *. imgDist) /. (objDist +. imgDist);
  };  
```

# Annotating Functions with Labeled Parameters

Consider this un-annotated function to calculate the total price, given a quantity, unit price, and tax as a percent. This function uses *labeled parameters*, specified with the `~`. When you call the function, you need to give the parameter name, but you can give the parameters in any order you like:

```reasonml
let totalPrice = (~qty, ~unitPrice, ~tax) => {
   (float_of_int(qty) *. unitPrice) *. (1.0 +. (tax /. 100.0));
};

let price1 = totalPrice(~qty=5, ~unitPrice=34.95, ~tax=7.5);
let price2 = totalPrice(~unitPrice=15.00, ~tax=5.0, ~qty=12);
```

When you annotate this function with the type information separated from the function definition (method 1), you need to name the parameters *in the same order* as in their declaration in the function:

```reasonml
let totalPrice: (~qty: int, ~unitPrice: float, ~tax: float) => float =
  (~qty, ~unitPrice, ~tax) => {
    (float_of_int(qty) *. unitPrice) *. (1.0 +. (tax /. 100.0));
  };
```

When using parameters-with-their-types (method 2), add the type information exactly as you did with unlabeled parameters.

```reasonml
let totalPrice =  (~qty: int, ~unitPrice: float, ~tax:float): float => {
   (float_of_int(qty) *. unitPrice) *. (1.0 +. (tax /. 100.0));
};
```

# Which Method to Use?

The main advantage of the separate specification (method 1) is that this is the format you use when creating a `.rei` (ReasonML interface) file. You use `.rei` files to specify an API for modules that you would like other people to use.

The main advantages of the parameter-with-type specification (method 2) are familiarity and the fact that you don’t have to specify a type for every parameter. If a parameter’s type is too complicated for you to figure out, you can leave it out and let the type inference system take over for you.

From what I’ve seen in code written by others, most people let the inference system do all the work. When they *do* annotate, they use method 2.

# Should You Annotate?

If you’re coming from a programming language where you have to specify types, I recommend that you annotate your ReasonML code as well.

If you’re coming from an untyped language, I recommend that you annotate your code as you’re learning ReasonML. This will get you used to thinking through exactly what kinds of input and output your functions need. Don’t worry about making mistakes&mdash;the type inference system will keep you honest!

Please let me know your thoughts in the discussion.