https://github.com/elixir-ecto/ecto/blob/master/lib/ecto/que...
Edit: Just remembered another use case where function overload is used to override default implementation
defmodule Downloader do
use GenServer
def handle_cast(:start, state) do
...
end
def handle_cast(:stop, state) do
...
end
end
Here, "use GenServer" imports default implementation of various handlers which can be overridden.https://github.com/elixir-lang/elixir/blob/main/lib/elixir/l...
def equal?(x,y) when x == y do
true
end
def equal?(_,_), do: false
would be equivalent to def equal?(x,y) do
case {x,y} do
{x,y} when x == y ->
true
_ ->
false
end
end
It's also handy in places like GenServers, where from a reading perspective clustering the hooks and their helper functions together is far more readable than the alternative. def handle_call(:msg1,_from,_state) do
helper1()
helper2()
etc...
end
def helper1(), do: 1
def helper2(), do: 2
def handle_call(:msg2,_from,_state), do: :err
Is a lot better than def handle_call(msg,_from,_state) do
case {msg, _from, _state} do
{:msg1,_from,_state} ->
helper1()
helper2()
etc...
{:msg2,_from,_state} -> :err
end
end
def helper1(), do: 1
def helper2(), do: 2
Especially remembering that you'll likely have a lot more than 2 message callbacks.If multiple function heads were used, you might have to repeat the typecheck guard every time, which is ugly in code, but probably not inefficient (the Erlang pattern/guard compiler is insanely good).
def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
assigns
|> assign(field: nil, id: assigns.id || field.id) # <-- ALLOWS TO AVOID RE-ENTERING THIS FUNCTION CLAUSE
|> assign(:errors, Enum.map(field.errors, &translate_error(&1)))
|> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end)
|> assign_new(:value, fn -> field.value end)
|> input() # <-- RECURSE WHICH ALLOWS TO CHOOSE THE RIGHT FUNCTION CLAUSE BELOW
end
def input(%{type: "checkbox", value: value} = assigns) do
...
end
def input(%{type: "select"} = assigns) do
...
end
... def factorial(0), do: 1
def factorial(n) when n > 0, do: n * factorial(n - 1)
Otherwise it comes down to a matter of taste. Sometimes they read nicer than a `case`, sometimes a `case` is better.Like I would favour:
def zero?(0), do: true
def zero?(_), do: false
over the case version, for example, but that's just me."Or just:"
def zero?(n), do: n == 0For functions of different arities, erlang (and therefor elixir) considers them completely different functions, and requires a declaration for each arity at least.
But then elixir adds optional args which confuses the answer a little. Being able to hard code handling of exceptional constants in a separate body is very useful but like you said it's conceptually the same thing as a pattern match, and not really an answer about why that feature is there.
I've looked a bit before but I haven't come up with anything more compelling than "erlang was kinda off doing its own thing, syntax-wise, and so some of it is just Like That now."
defmodule Foo do
def bar(baz \\ "baz") do
baz
end
end
becomes defmodule Foo do
def bar do
"baz"
end
def bar(baz) do
baz
end
end
Which, as you pointed out, are two completely separate functions.The syntax makes a lot more sense if you go into the history (well-documented) and understand that Erlang's initial implementation was in Prolog and a lot of the syntax can be seen as a consequence of that.
As for pattern matching in the function signature instead of forcing dropping to a case expression, it removes a level of useless and noisy indentation and, again, is a lot more like Prolog.
Even the use of , and ; as separators in Erlang make sense given that heritage. In Prolog a sequence like this:
A, B, C.
Can be read as "A and B and C" while: A; B; C.
Can be read as "A or B or C". So function bodies using , as a separator and ; between alternatives with . as a terminal follows straight from Prolog.another use case that has not been mentioned is generated functions
you can't generate case statements as easily