Debugging tricky parameterized types
Tags: debugwithme, tipstricks, rescript, types
Published: 2020-12-11 21:11:28

---
title: Debugging tricky parameterized types
published: true
date: 2020-12-11 20:51:32 UTC
tags: DebugwithMe,TipsTricks,rescript,types
canonical_url: https://mrmurphy.dev/debugging-tricky-parameterized-types/
---

# Debugging tricky parameterized types in Rescript

Parameterized functions are fantastic. I'm talking about the functions that operate _around_ some abstract piece of data, without doing anything that would require specific knowledge about that data. Take, for example, the following fictional function:

## Example

```
let maybeSaveThing = (maybeThing: option<debug>): option<debug> => {
  switch maybeThing {
  | Some(thing) => saveThing(thing)
  | None => None
  }
}
```

The code above is designed to take an option of _something_, save it somewhere if the option contains data, and return the result. We assume, for this example, that the `saveThing` function knows now to save anything. We can tell that the function shouldn't care what's contained in the option type by looking at the function signature. It takes an `option<'a>`, and returns an `option<'a>`.

## A Sneaky Bug

But this code has a sneaky bug. We can see it if we look at the function's inferred type:

```
option<int> => option<int>
```

The compiler is telling us that the function takes an option of an integer, even though we explicitly stated in the type signature that the function should be generic. Why is that?

## Why Don't we Get a Type Error?

Generics encapsulate all other types, that's why. For example, an `option<int>` _is_ an `option<'a>` as far as the type system is concerned. So is an `option<string>` or a `option<blah>`. If a part of the code inside of a function returns a specific value where a generic value was expected, the type inference algorithm quietly turns the function's parameter into a specific type. The compiler won't tell you that your type annotation is wrong, because it _isn't_. An `option<int> => option<int>` _is_ an `option<'a> => option<'a>`.

## Why Are We Getting an `int`?

Spoiler alert, there's a call inside of our function that returns an int where the generic type was expected. Stick around and we'll see how to find it in a moment.

## Discovering the Mismatch

We intended the type to be generic. It's troublesome that we don't discover this breakage until we want to use this function to save some type other than an int. The compiler will tell us that `maybeSaveThing` takes an `option<int>` and we're trying to pass in something like `option<string>`.

How can we discover these unintentional losses of genericism closer to the site of definition?

## Enforcing Genericism

Adding an interface `(.resi)` file with the generic signature for the function will help the compiler out. Instead of inferring the type of the function to be specific, the compiler will know that you intend to keep that function generic, and it'll tell you that your implementation doesn't match the interface.

## Finding the Mistake

Unfortunately, an error that the function definitions don't match doesn't go far to help us find the location of the mistake. When we're dealing with more than mere example code, and we have a complex function with lots of operations, it's tempting to abort the whole effort and scroll through Twitter instead.

But here's a trick that can speed the process up:

```
type debug
```

We've added a new type to our code that is the opposite of generic. It's so specific that no value fits it. And it's explicitly for debugging, so we know that no other area of the code will rely on this type for business logic.

If we swap out the generic argument in our function definition with `debug` temporarily:

```
type debug

let maybeSaveThing = (maybeThing: option<debug>): option<debug> => {
  switch maybeThing {
  | Some(thing) => saveThing(thing)
  | None => None
  }
}
```

It'll show us the line of code that is returning a specific, non-generic value:

```
  8 │ let maybeSaveThing = (maybeThing: option<debug>): option<debug> => {
   9 │ switch maybeThing {
  10 │ | Some(thing) => saveThing(thing)
  11 │ | None => None
  12 │ }

  This has type: option<int>
  Somewhere wanted: option<debug>

  The incompatible parts:
    int vs debug
```

Aha! The call to `saveThing` is returning a `option<int>`. If that had been a generic function, its return type would have been `option<debug>`. Let's look at its definition:

```
let saveThing = _thing => {
  Some(21)
}
```

There's the source of the problem. If we modify it to do something generic instead:

```
let saveThing = thing => {
  let _ = Js.Json.stringifyAny //-> blah blah do some saving in the background
  Some(thing)
}
```

Then the compiler is happy, and we know our code is generic again. We can remove `debug` and put the type parameters back in place.