I feel like every time I reach for the lists module I get complaints about types from ELP.
-spec bad(X :: [number()]) -> [number()].
bad(X) ->
lists:reverse(X).
With the elp eqwalize error,
error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types)
┌─ src/example.erl:22:5
│
22 │ lists:reverse(X).
│ ^^^^^^^^^^^^^^^^
│ │
│ lists:reverse(X).
Expression has type: [term()]
Context expected type: [number()]
│
Because in the expression's type:
[
Here the type is: term()
Context expects type: number()
]
I personally find this quite frustrating. This happens with lots of functions from lists due to the use of term() in specs. Is there an obvious reason for this? Would it be useful to have a library with types like,
-spec reverse([T]) -> [T].
-spec foldl(fun((T, Acc) -> Acc), Acc, [T]) -> Acc.
I’m more than happy to create that but I’d also love to understand the historical context.
Part of the problem is that this does not mean what you think it means. When a type variable is used in multiple places it means that it is the value that is identical, not the type of the value. For example:
-spec add(A) -> A.
add(A) -> A + 1.
is an invalid spec as the value passed into the function is not the same as the value returned. The correct spec would be:
-spec add(integer()) -> integer().
There is (currently) no way to express that the type of the value is the same type as the return. I think Equalizer works around this in someway, but dialyzer will start to complain if you start rewriting specs to use type variables in the way you suggest.
If you search online/ask an LLM there are better explanations out there.
I’m unable to replica this using ELP otp-28-2026-06-10 (via asdf) in vscode.
Your exact function and spec does not emit a type error for me. Maybe it’s an ELP version issue?
eqwalizer has a set of “classic” overrides to make the OTP specs more friendly in eqwalizer_specs.erl. See here for instructions how to use them erlang-language-platform/eqwalizer at main · WhatsApp/erlang-language-platform · GitHub.
We want to upstream them, but it got a bit stuck in this PR.
This isn’t really true since EEP 71, and the type variables are defined as generics (see point 3 in the EEP):
Treat type variables as generics rather than as equality constraints.
1 Like
I had some vague recollection about this but assumed it was just talk and never got implemented. Thanks for finding the link and correcting me.
Hope you find time to work on it again soon! Would be great to have them aligned where possible.
2 Likes