fireproofsocks

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

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

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

fireproofsocks

What exactly are “overlapping specs”?

ityonemo

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.

Where Next?

Popular in Questions Top

srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
pgiesin
This should be a simple problem but I just can’t seem to figure it out. I have a standalone Elixir app that won’t find the database. Dep...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 45766 226
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> someth...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

We're in Beta

About us Mission Statement