fireproofsocks
Writing multiple specs for multiple function clauses? Or single spec?
I’m confused… when grooming the docs for https://elixir-lang.org/getting-started/typespecs-and-behaviours.html I got the feedback that multiple function clauses require multiple @specs. But now that I’m digging into dialyzer, I’m getting errors in places where I’ve done that. E.g.
Overloaded contract for MyApp.some_function/1 has
overlapping domains; such contracts are currently unsupported and
are simply ignored.
Consider this module:
defmodule MyApp.Helpers do
alias Absinthe.Blueprint.Input.String, as: AbsintheString
alias Absinthe.Blueprint.Input.Integer, as: AbsintheInteger
alias Absinthe.Blueprint.Input.Float, as: AbsintheFloat
@spec parse_value(AbsintheString.t() | AbsintheInteger.t() | AbsintheFloat.t() | any()) :: {:ok, String.t()} | {:error, any()}
def parse_value(%AbsintheString{value: value}) do
{:ok, value}
end
def parse_value(%AbsintheInteger{value: value}) do
{:ok, Integer.to_string(value)}
end
def parse_value(%AbsintheFloat{value: value}) do
{:ok, Float.to_string(value)}
end
def parse_value(_) do
{:error, "Invalid value"}
end
end
Dialyzer seems happy when it has only a single combined @spec, but readability is better when each function clause has its own @spec, something more like this:
@spec parse_value(AbsintheString.t()) :: {:ok, String.t()}
def parse_value(%AbsintheString{value: value}) do
{:ok, value}
end
@spec parse_value(AbsintheInteger.t()) :: {:ok, String.t()}
def parse_value(%AbsintheInteger{value: value}) do
{:ok, Integer.to_string(value)}
end
# ... etc...
Which way is correct?
Marked As Solved
michallepicki
You’re only getting this warning because of the fallback function clause with any. If types for all function arguments overlap, Dialyzer will show a warning (not an error AFAIK) and (I think?) it will use only the last version.
Multiple specs are sometimes useful when you can have different argument types and maybe different return types for them. Separate @spec can look cleaner than a big one with many | ... | ....
But if specs are the same (or overlapping) I would probably use just one. Notice what ex_doc generates for the same specs: it will just list the same thing multiple times.
Also Liked
NobbZ
Both are correct.
Use whichever you prefer, though having multiple specs can lead to “overlapping” specs, which are not allowed.
Also ... | ... | any can be simplified to any.
Last but not least, dialyzer will combine the second version into the first anyway.
fireproofsocks
What exactly are “overlapping specs”?
ityonemo
In the example you gave the two typepecs are quite literally identical, so the preimage and range of both “functions” trivially overlaps. Here’s a nonoverlapping typespec with associated function:
@spec foo(a :: :give_me_a_string) :: String.t
@spec foo(a :: :give_me_a_list) :: list
def foo(:give_me_a_list), do: 'foo'
def foo(:give_me_a_string), do: "foo"
I prefer to group my typespecs for distinct function entry points (this is rare) together, and I personally organize spec, doc, function bodies. But these are stylistic choices.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








