You are correct: Elixir uses any "excess" arguments provided to override a function's default values for some or all of its parameters, filled from left to right. My understanding of this came from the Programming Elixir 1.6 book by Dave Thomas.
The trick is that for functions with default arguments, the compiler actually generates multiple functions. A function defined like this:
defmodule Example do
def func(p1, p2 \\ 2, p3 \\ 3, p4) do
IO.inspect [p1, p2, p3, p4]
end
end
Actually gets compiled down to a module that looks something like this:
defmodule Example do
def func(p1, p4) do
func(p1, 2, 3, p4)
end
def func(p1, p2, p4) do
func(p1, p2, 3, p4)
end
def func(p1, p2, p3, p4) do
IO.inspect [p1, p2, p3, p4]
end
end
By the way, you can see this by using a module's "magic" module_info/0 function, e.g. Example.module_info().
Calling your functions with some sample values will help you understand how they are interpreted, and there may be a few surprises.
Example.func("a", "b") # => ["a",2,3,"b"]
Example.func("a", "b", "c") # => ["a","b",3,"c"] <--- !!!
Example.func("a", "b", "c", "d") # => ["a","b","c","d"]
It may be a bit easier to see this behavior with a function like this:
def func(p1 \\ 1, p2 \\ 2) do
IO.inspect [p1, p2]
end
And calling it with only 1 argument provided. It will use the provided argument on the left side; the default on the right still gets used:
func("a") # => ["a", 2]
I agree that things do get confusing when a function has optional arguments that aren't at the end of the argument list.