Please help me understand the following typespec

I was going through the phoenix channel code and found the following typespec

 @type reply :: status :: atom | {status :: atom, response :: map}

I don’t understand the point of writing

:: status 

It would have made sense to me if it was only

@type reply :: atom | {status :: atom, response :: map}

Thanks

@Mukulch15 Hmm, that’s interesting! … I also see it for first time.

Let’s assume that status as atom is :ok and response as map is %{}.

From what I understand after seeing code this gives us:

status = :ok
response = %{}
reply = status
# or
reply = {status, response}

That’s said I think that such code would be much more readable:

@type reply :: status | {status, response :: map()} when status: :atom 

However … it does not work …

** (CompileError) iex:2: invalid type specification: reply :: status | {status, response :: map()} when status: :atom
    (elixir 1.11.2) lib/kernel/typespec.ex:911: Kernel.Typespec.compile_error/2
    (stdlib 3.13.2) lists.erl:1267: :lists.foldl/3
    (elixir 1.11.2) lib/kernel/typespec.ex:226: Kernel.Typespec.translate_typespecs_for_module/2
    (elixir 1.11.2) src/elixir_erl_compiler.erl:12: anonymous fn/3 in :elixir_erl_compiler.spawn/2

I was really surprised seeing this, because I thought that’s supported when it’s actually not!

I have navigated to:

What I found surprising is that’s supported, but only in @spec (there are no equivalent examples for @type). This means we can write:

iex> defmodule Example do                                                    
...>   @spec reply() :: status | {status, response :: map()} when status: :atom
...>   def reply, do: :ok
...> end
{:module, Example,
 <<70, 79, 82, 49, 0, 0, 5, 32, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 142,
   0, 0, 0, 15, 14, 69, 108, 105, 120, 105, 114, 46, 69, 120, 97, 109, 112, 108,
   101, 8, 95, 95, 105, 110, 102, 111, 95, ...>>, {:reply, 0}}

Summary:

  1. It looks like @type have limited functionality compared to @spec i.e. it does not support guards.
  2. It looks like code you mentioned is using some kind of workaround for @type limitation.

Note: This is only my opinion after a quick analyze which may or may not be confirmed by it’s maintainers like @chrismccord.

1 Like

It’s just about giving the type a name that can show up in docs etc, instead of atom, status :: atom explains what the value is.

I’m not very used to writing/seeing when in typespecs, @Eiji, but yeah I guess that looks more concise indeed :+1:

1 Like

@spec does support the “when” notation because this is needed when the output type depends on the input type @spec something(x) :: x, but you also want to restrict the types at the same time: @spec something(x) :: x when x: atom.

Type declarations don’t need the the first part, so they don’t support the latter.

1 Like

Thanks for the detailed description. I was seeing @type reply :: status and :: atom | {status :: atom, response :: map} separately. Now it makes sense.