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

baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a > b) do {:ok, "a"} end if (a < b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
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
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

Other popular topics Top

rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36820 110
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New

We're in Beta

About us Mission Statement