stephen_m
Multiple clauses, defaults and conflicts
Hi,
When writing the following code, elixir correctly and thankfully warns that there is a conflict:
function/macro 1:
defmacro static_translate(key, opts \\ [humanize: :true, downcase: :true]) do
... do stuff here
end
function/macro 2:
defmacro static_translate(module, key, opts \\ [humanize: :true, downcase: :true]) do
...do other stuff here
end
error
web/views/views_translation_helpers.ex:59: defmacro static_translate/3 defaults conflicts with defmacro static_translate/2
I’ve been dealing with these situations a few times and I’m never quite happy with my refactoring…
how would you rewrite this?
(please assume the function logic is sufficiently different to warrant a separate function completely)
Marked As Solved
NobbZ
The only possible way to refactor here is to rename one of the macros/functions.
def foo(bar, baz \\ []), do: ...
def foo(quux, bar, baz \\ %{}), do ...
is getting expanded at compile time to the following:
def foo(bar), do: foo(bar, [])
def foo(bar, baz), do: ...
def foo(quux, bar), do: foo(quux, bar, %{})
def foo(quux, bar, baz), do: ...
As you can see, there are now 2 foo/2, which is the mentioned conflict in the error you posted.
Also Liked
peerreynders
Welcome to the forum!
same({1,2},3)
could be interpreted verbatim as calling same/2 or as
same({1,2}, 3, 1)
due to the optional parameter on same/3.
How is the compiler supposed to know which one you mean?
Don’t mistake pattern matching for static typing.
Note:
iex(1)> defmodule Test do
...(1)> # same/3
...(1)> def same(_a, _b, _c \\ 1), do: 3
...(1)> # same/2
...(1)> def same({_a, _b}, _c), do: 2
...(1)> # same/1
...(1)> def same({_a, _b}), do: 1
...(1)> end
** (CompileError) iex:5: def same/2 conflicts with defaults from same/3
iex:5: (module)
iex(1)>
i.e. you should have gotten an error on your first example.
Your ordering happened to create this:
iex(1)> defmodule Test do
...(1)> # same/1
...(1)> def same({_a, _b}), do: 1
...(1)> # same/2
...(1)> def same({_a, _b}, _c), do: 2
...(1)> def same(a, b), do: same(a, b, 1)
...(1)> # same/3
...(1)> def same(_a, _b, _c), do: 3
...(1)> end
{:module, Test,
<<70, 79, 82, 49, 0, 0, 4, 240, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 123,
0, 0, 0, 13, 11, 69, 108, 105, 120, 105, 114, 46, 84, 101, 115, 116, 8, 95,
95, 105, 110, 102, 111, 95, 95, 7, 99, ...>>, {:same, 3}}
iex(2)> Test.same({:ok, :ok})
1
iex(3)> Test.same({:ok, :ok}, :ok)
2
iex(4)> Test.same(:ok, :ok, :ok)
3
iex(5)> Test.same(:ok, :ok)
3
iex(6)>
The behaviour you are looking for seems to be this:
iex(1)> defmodule Test do
...(1)> # same/1
...(1)> def same({_,_} = arg), do: same(arg, 1)
...(1)> # same/2
...(1)> def same({_a,_b}, _c), do: 2
...(1)> def same(a,b), do: same(a,b, 1)
...(1)> # same/3
...(1)> def same(_a, _b, _c), do: 3
...(1)> end
{:module, Test,
<<70, 79, 82, 49, 0, 0, 5, 8, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 123, 0,
0, 0, 13, 11, 69, 108, 105, 120, 105, 114, 46, 84, 101, 115, 116, 8, 95, 95,
105, 110, 102, 111, 95, 95, 7, 99, ...>>, {:same, 3}}
iex(2)>
nil
iex(3)> Test.same({:ok,:ok})
2
iex(4)> Test.same({:ok,:ok}, :ok)
2
iex(5)> Test.same(:ok, :ok)
3
iex(6)> Test.same(:ok, :ok, :ok)
3
iex(7)>
peerreynders
That is actually
# same/1
def same({a, b}), do: same({a, b}, 1)
# same/2
def same({a, b}, c), do: ...
def same(a, b), do: same(a, b, 1)
# same/3
def same(a, b, c), do: ...
which is equivalent to
# same/1
def same({a, b}), do: same({a, b}, 1)
# same/2
def same(arg, c) do
case arg do
{a,b} -> ...
_ -> same(arg, c, 1)
end
end
# same/3
def same(a, b, c), do: ...
Optional parameters are not a feature of the underlying language - Erlang. It’s something that Elixir layers on top in a way that “works most of the time as expected”. But there can be surprising edge cases.
This creates ONE function (same/2):
def same({a, b}, c), do: ...
def same(a, b), do: ...
This creates TWO functions (same/2 and same/3):
def same(a, b, c \\ 1), do: ...
Taking that into account
# this emits code for same/1 and same/2
def same({a, b}, c \\ 1), do: 2
# this emits code for same/2 and same/3
def same(a, b, c \\ 1), do: 3
The error probably relates to the fact that two separate clauses with optional function parameters emit code for the same function: same/2.
The intent that is behind
def same({_a,_b}, _c \\ 1), do: 2
def same(_a, _b, _c \\ 1), do: 3
cannot be implemented with optional parameters but can be implemented long-hand
def same({_,_} = arg), do: same(arg, 1)
def same({_a,_b}, _c), do: 2
def same(a,b), do: same(a,b, 1)
def same(_a, _b, _c), do: 3
davearonson
Looks to me like you’re saying module is optional… so maybe you could make module one of the opts?
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #javascript
- #code-sync
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








