Definitions with multiple clauses and default values require a function head

Hi all

Consider following code:

defp filter_content_type([h|t], acc \\ %{}) when is_map(acc) do

  content_type = try do
                   {@content_type, content} = h
                   {:ok, content}
                 rescue
                   _ in MatchError -> {:error, %{}}
                 end

  case content_type do
    {:ok, content} ->
      acc = Map.put(acc, @content_type, content)
      filter_content_type([], acc)

    _ -> filter_content_type(t, acc)
  end
end

defp filter_content_type([], acc) when is_map(acc) do
  acc
end

When I compile the code, I’ve got following warning:

    warning: definitions with multiple clauses and default values require a function head. Instead of:

        def foo(:first_clause, b \\ :default) do ... end
        def foo(:second_clause, b) do ... end

    one should write:

        def foo(a, b \\ :default)
        def foo(:first_clause, b) do ... end
        def foo(:second_clause, b) do ... end

        defp filter_content_type/2 has multiple clauses and defines defaults in a clause with a body
          web/controllers/odata_controller.ex:46

My question, how can I improve the code to avoid warning?

Thanks

How to remove the warning is written very clear and with wonderfull examples in the warning itself.

In your particular case, it should look like this:

defp filter_content_type(list, acc \\ %{})
defp filter_content_type([h|t], acc) when is_map(acc) do
  # Stuff
end

defp filter_content_type([], acc) when is_map(acc) do
  acc
end
2 Likes

If you have a function with multiple clauses and defaults you need to define a ‘function head’ to define what the default arguments are.

In your case you’d need to have something like:

defp filter_content_type(l, acc \\ %{}) # this is the function head
defp filter_content_type([h|t], acc) when is_map(acc) do ... end
defp filter_content_type([], acc) when is_map(acc) do ... end

The advantage to doing things this way is that it is very clear how defaults are produced: we look at the function head, introduce the defaults, and then do pattern matching on the non-head clauses.

3 Likes

The warning tells you that, to avoid ambiguity, it is required to have a function head without a body when you use a default value.

Instead of:
        def foo(:first_clause, b \\ :default) do ... end
        def foo(:second_clause, b) do ... end
one should write:
        def foo(a, b \\ :default)
        def foo(:first_clause, b) do ... end
        def foo(:second_clause, b) do ... end

So let’s replace the function used in the example with the clauses of the function it is complaining about (hiding the bodies for brevity):

Instead of:
        defp filter_content_type([h|t], acc \\ %{}) when is_map(acc) do ... end
        defp filter_content_type([], acc) when is_map(acc) do ... end

one should write:
        defp filter_content_type(list, acc \\ %{})
        defp filter_content_type([h|t], acc) when is_map(acc) do ... end
        defp filter_content_type([], acc) when is_map(acc) do ... end

Do you see what is going on here? :slight_smile:

3 Likes

I can not see the difference. What is the purpose?

Well, you did not ask for a purpose the first time, but the purpose is disambiguity. It is just how elixir is designed.

If you have multiple clauses AND default values for arguments you NEED to explicitely “declare” that default arguments in a body-less function clause.


In your original example, you could spontaniously have the idea to add an default value to acc of the second clause, which would add an ambiguity, therefore you have to extract default values. That way it is guaranteed that there will be never more than a single possibility for a default value.

3 Likes

Thanks so much guys for helping.

1 Like

Will never be executed right?

It doesn’t need to.

It just tells the compiler, that there will be a private function with the name filter_content_list with arity 2. Also it tells the compiler, that the second argument shall have a default value of %{}.

In fact, def foo(list \\ []), do: # stuff will be compiled into a function foo/0 which does deligate the call to foo/1:

def foo(), do: foo([])

def foo(list), do: #stuff

And something similar does happen for your original code.

1 Like

Did you mean to include the default \\ %{} in both the function head and the first clause? If not, then perhaps edit to fix? :thinking:

Nope, that was a C&P-error, I have fixed it

1 Like