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 (
B) are passed straight away to the
+ operator. That implies that, for the
sum/2 function not to fail,
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
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:
This feature is enabled by default in Erlang LS. Like for any other
code lens, the feature can be disabled via the
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!