Skip to content

Suggesting Type Specs

Erlang is a strict, dynamically typed, functional programming language. When implementing an Erlang function, it is not required to include any explicit information about the types of the input parameters, nor of the return value of the function. While this can sometimes be seen as convenient, in the sense that it allows fast prototyping of a function, it has the heavy drawback that type errors can occur at runtime, when it is too late. Lack of type information can also make it harder to understand the purpose of a function, given its signature.

In the Erlang ecosystem, a tool named dialyzer exists to help the programmer to identify software discrepancies such as type errors via a static analysis.

While Dialyzer works by inferring type information using a technique based on success typings, it is possible to explicitly annotate an Erlang function with type information. Adding type specifications to a function is particularly useful for the programmer when reading and reasoning about code, since they give an overview of the function contract.

Given the function sum/2 which computes the sum of two numbers:

sum(A, B) ->
  A + B.

We can annotate it with type specifications:

-spec sum(number(), number()) -> number().
sum(A, B) ->
  A + B.

For more information about type specifications and their syntax, please refer to the official reference manual.

Type specifications can often be programmatically inferred. If we look again at the sum/2 function above, we can see that the two input values for the function (A and B) are passed straight away to the + operator. That implies that, for the sum/2 function not to fail, both A and B (and the return value of the function itself!) must be numbers. This is essentially how - well, in a very simplifyied way - a less known tool, named Typer, works under the hood to generate type specifications for functions which lack them.

Erlang LS today leverages both Dialyzer and Typer to make it possible for the programmer to generate type specifications directly from the text editor.

First Time Setup

To do its job, Dialyzer (and therefore Erlang LS) makes use of a Persistent Lookup Table (a.k.a. PLT). This table needs to be generated before you can use this feature in Erlang LS. Generating a PLT is a simple operation that can be achieved via:

dialyzer --build_plt --apps erts kernel stdlib

Where you can of course customize the provided list of applications. For more information about creating a PLT and how to later update it, please refer to the Dialyzer User Guide.

Whenever a function lacks type specifications, you will see a Add spec code lens next to the function definition. By clicking on the lens (or by using a keyboard shortcut), Erlang LS will attempt at suggesting type specifications for your function. This is what the procedure looks like in Emacs:

Suggest Specs

This feature is enabled by default in Erlang LS. Like for any other code lens, the feature can be disabled via the erlang_ls.config file, using the following configuration:

lenses:
  disabled:
    - suggest-spec

To make this possible, we had to fork the typer program from Erlang/OTP, mostly because the tool was designed as a separate Command Line utility and not to be invoked from Erlang code. This is something that should be easy to address in Erlang/OTP itself, avoiding the need of a fork in the future.

There are a few other things to take into account when using this feature, most of which could be addressed in Typer itself:

  • The function signatures do not include spaces after commas, making linters complain
  • When producing records, the output is extremely verbose (containing types for all fields) and that should be simplified
  • When user defined aliases exist for a given type, they should be used (this can be tricky to implement)

Finally, other tools such as Gradualizer could be considered and eventually integrated in Erlang LS.

For now, I hope you enjoy!