paseg

paseg

@behaviour, @callback and @spec

Hi

When I use @behaviour and @callback, the functions are defined. I guess that I do not need to use @spec for the implementation of the @impl functions as well?

Will Dialyzer sort this out as well?

Br Patrik

Most Liked

asummers

asummers

You don’t need them as Dialyzer will give a callback does not match spec error, but please include them anyway. As a reader I do not want to have to jump to the behaviour definition to find out what the arguments are. In that vein if you include a using macro where you define default callbacks that are overridable with defoverridable, elide the spec in the using macro, otherwise you’ll get a compiler error for duplicating the spec if anyone actually overrides and wants to spec the override.

YES

defmodule MyBehaviour do
  @callback foo() :: :ok
end

defmodule MyImpl do
  @impl MyBehaviour
  @spec foo() :: :ok
  def foo(), do: :ok
end

YES

defmodule MyBehaviour do
  @callback foo() :: :ok

  defmacro __using__(_) do
    quote do
      @impl MyBehaviour
      @spec foo() :: :ok
      def foo(), do: :ok
    end
  end
end

defmodule MyImpl do
  use MyBehaviour
end

YES

defmodule MyBehaviour do
  @callback foo() :: :ok

  defmacro __using__(_) do
    quote do
      @impl MyBehaviour
      def foo(), do: :ok
    
      defoverridable [foo: 0]
    end
  end
end

defmodule MyImpl do
  use MyBehaviour

  @impl MyBehaviour
  @spec foo() :: :ok
  def foo(), do: :ok
end

NO

defmodule MyBehaviour do
  @callback foo() :: :ok

  defmacro __using__(_) do
    quote do
      @impl MyBehaviour
      @spec foo() :: :ok
      def foo(), do: :ok
    
      defoverridable [foo: 0]
    end
  end
end

defmodule MyImpl do
  use MyBehaviour
  
  # this errors if you include the spec
  # @spec foo() :: :ok
  @impl MyBehaviour
  def foo(), do: :ok
end
paseg

paseg

Hi

Wow, thanks for all comments! Did not know that this would stir up this many opinions. :slight_smile:

A see your point @asummers, but since this is not a public library (“only” used within our company), I prefer that the implementers spend the extra time to go into the definition of the behaviour rather than using multiple specs that will effect the maintenance in the long run.

I also found that the @spec may state less than the actual @callback without Dialyzer telling me, witch gives me another argument not to use the @specs

Example given:

defmodule Register.DocEvents do
  @callback initialize(soure :: binary() | atom()) :: :ok | {:error, String.t()}
end
defmodule Register do
  @behaviour Register.DocEvents

  @impl Register.DocEvents
  @spec initialize(atom()) :: :ok
  def initialize(source) do
    ...
  end
end

Dialyzer signals this is ok, and I guess it is since the actual implementation fits within the original specification. In this case, the @spec makes sense since this implementation is not the same as the @callback stated, but if they are expected to be the same then adding an extra @spec just creates more maintenance burden.

Eiji

Eiji

Sure, here is your changed code:

defmodule MyBehaviour do
  @callback foo() :: :ok
end

defmodule MyImpl do
  @behaviour MyBehaviour

  @doc delegate_to: {MyBehaviour, :foo, 0}
  @doc "Implementation-specific docs goes here …"
  @impl MyBehaviour
  def foo(), do: :ok
end

which would give:

iex(1)> h MyImpl.foo       

                                   def foo()                                    

delegate_to: MyBehaviour.foo/0

Implementation-specific docs goes here …

iex(2)> b MyBehaviour.foo/0
@callback foo() :: :ok

This is much simpler than writing macros or copy-paste documentation and spec.

Generally we should avoid using macros unless it’s required.

Last Post!

yatender-oktalk

yatender-oktalk

Thank you for the discussion everyone!
it helped me really to read the different perspectives and make decisions.

Where Next?

Popular in Questions Top

minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New

Other popular topics Top

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
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
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
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 44139 214
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
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

We're in Beta

About us Mission Statement