What do function heads accomplish in the case of default arguments?

I’m most likely digging up buried skeletons, but I really don’t see why functions heads are mandatory when working with default arguments and multiple clauses. I’ve looked at the official docs (http://elixir-lang.org/getting-started/modules.html#default-arguments), seen a similar question (Definitions with multiple clauses and default values require a function head) and even posted this on StakOverflow (http://stackoverflow.com/questions/41356447/whats-the-need-for-function-heads-in-multiple-clauses) but could’t get a satisfactory answer.

I fail to see how having a function head helps me avoid ambiguity. On a different but related note, if Elixir really wanted to avoid ambiguity, it wouldn’t let something like this pass by with a mere warning:

defmodule MyTest do
  def func(a, _), do: "I always match"
  def func(a, b), do: "I never match!"
end

In the cases of ambiguity, I feel that this warning is more than sufficient and function heads really don’t server a purpose. Can someone point to a simple but useful example of how function heads help?

P.S. I’m also interested in the pull request that made this change. Maybe going through that discussion will help.

The reason is that default arguments are syntactic sugar around a ‘double’ definition:

def foo(a, b \\ "default") do
  IO.puts({a, b})
end

# Desugars to:

def foo(a) do
  foo(a, "default")
end

def foo(a, b) do
  IO.puts({a, b})
end

This means that if you have multiple other function clauses that accept two arguments, it is unclear which one should be called by the automatically generated one-argument version. So to resolve this ambiguity, Elixir requires you to specify the default arguments as a separate (body-less) function head in these cases.

I can understand what you’re saying, but I can’t work it out in my head. Suppose I have the following clauses:

# Clause 1
def foo(a, b \\ nil, c) do
end

# Clause 2
def foo(a, b, c \\ nil) do
end

Now, Clause 1 will, according to you, “de-sugar” into:

Clause 3
def foo (a, c) do
  foo(a, nil, c)
end

and

Clause 4
def foo (a, b, c) do      
end

while Clause 2 will “de-sugar into”:

#Clause 5
def foo(a, b) do
  foo(a, b, nil)
end

and

Clause 6
def foo (a, b, c) do      
end

It now looks like it’s the other way around: clause 6 and clause 4 are clashing, and I don’t see how a function head would save this situation.

Possible for you to give a more detailed example? :slight_smile:

Try your own example in the console please. It won’t work.

** (CompileError) iex:4: 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

def foo/3 has multiple clauses and defines defaults in a clause with a body

Is that comment intended for me? (sorry I can’t tell which comment is in reply to which on this forum). If so, the example in my original post was compiled and checked. The other one, with many clauses, was made up in response to Qqwy above. I’m not sure from where you’ve picked up the example.

As your example shows, there’s a clash. Function head helps, because we split argument expansion from the implementation clauses. So when you write a bodyless declaration:

def foo(a, b \\ nil, c \\ nil)

You’ll get following definitions:

def foo(a), do: foo(a, nil)
def foo(a, b), do: foo(a, nil, nil)

The bodyless declaration took care of expanding arguments by providing implementations of foo/1 and foo/2.

Notice that at this point, foo/3 is not implemented. However, since default values are now taken care of, you can easily implement that function without worrying about defaults:

def foo(pattern1, pattern2, pattern3), do: # ...
def foo(pattern4, pattern5, pattern6), do: # ...

Since you don’t care about default arguments anymore, you can now avoid clashes.

3 Likes

Thank you! Finally makes sense. :smile:

Sometimes it is just easier to write the functions yourself so everything becomes explicit.

2 Likes

I know this is almost 2 years old, but this is the only post that I can find that explains this well…but I am wondering should def foo(a, b), do: foo(a, nil, nil) in your answer be def foo(a, b), do: foo(a, b, nil)?

1 Like

Yes, it should be.

Good catch! In fact, I got the first clause wrong too :slight_smile:

Instead of

def foo(a), do: foo(a, nil)

it should be

def foo(a), do: foo(a, nil, nil)
2 Likes