3 ms·
This is a good overview on ADTs and how to write them in your typespecs. Unfortunately, it doesn't dive into how exactly they interact with dialyzer, which in m
by losvedir 4y ago
This is a good overview on ADTs and how to write them in your typespecs. Unfortunately, it doesn't dive into how exactly they interact with dialyzer, which in my experience is not perfect.
Since dialyzer does "success typing", it will allow code that could work in the happy path, even if it has type issues, which is very limiting. You can make it stricter with `overspecs` and `underspecs`, which will flag if the inferred type is either stricter or looser than the given spec, and these are so close to what I want, but they don't quite work with sum types as in the article here.
The issue is dialyzer considers "too loose" or "too strict" on both function inputs and outputs, but really what you want is to warn only if the function returns more than the spec allows or accepts less than the spec allows. (This is, I believe, covariance and contravariance in types[0].) In other words, you want half of each of underspecs and overspecs.
Consider the example in the article of `@type direction :: east | north | west | south`. Then, it's perfectly valid for a function to return only a subset of those:
@spec common_wind() :: direction
def common_winds(), do: :east
But with `underspecs` this gets flagged. (This would be fine in Rust, for example. If you say a function returns an enum, you don't need it to return every variant of that enum.)
On the other hand, this is what you want on the input. If you specify `@spec foo(direction)`, then it better handle every variant of that enum.
The opposite problem is a function that can, in theory, handle more than specified. For example:
@spec func_that_can_handle_null(direction | null) :: boolean
def func_that_can_handle_null(arg), do: ...
@spec my_func(direction) :: boolean
def my_func(arg) do
func_that_can_handle_null(arg)
end
In this case, you want to express to callers of your function that `null` is not acceptable. However, incidentally from the implementation, it is (for now). In this case, dialyzer will infer that the function can handle `direction | null`, and see that your spec only has `direction`, and so the `underspecs` flag will now warn against it! But there's no problem here. The problem function inputs can have is the reverse: if you specify `direction | null` but then don't handle the `null`. That's the `overspecs` flag, but then that will mess you up on the first example!
OTP 25 introduced `missing_returns` and `extra_returns`, which is "half" of it, only checking the return side, so I'm thrilled we're finally going in the right direction. But until we have `missing_inputs` and `extra_inputs`, I will continue to think that dialyzer doesn't really support ADTs.
[0] https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance https://docs.microsoft.com/en-us/dotnet/standard/generics/co...
- scythmic_waves 4y agoThis was a very helpful explanation of why dialyzer yells at me sometimes and I can't figure out why. Is it possible to analyze a particular function with `underspecs` or `overspecs` without affecting the rest of the code?