Hi everyone, I have a function with two parameters with default values something like:
def(param1 \\ %{}, param2 \\ "") do
###
end
this is the best practice for do that? or you know another way ?
Hi everyone, I have a function with two parameters with default values something like:
def(param1 \\ %{}, param2 \\ "") do
###
end
this is the best practice for do that? or you know another way ?
You can also use functions from the Keyword module
def foo(opts \\ []) do
arg1 = Keyword.get(opts, :arg1, "default_value")
...
end
I’ve learned the hard way that if you ever have more than one optional argument its a smell and you should refactor it to an opts
list
amazing! @srcoulombe and @zachdaniel thanks for you advice
Absolutely never do this. Your colleagues or even your own future self will forget or be in a hurry just once and introduce a bug that would be difficult to track down.
Either only one optional argument and it must 99% of the time be the last one – unless the differences in type will make it completely obvious if something goes wrong – or use a keyword list as others have said. That approach also allows you to easily name stuff f.ex.
do_stuff(repo, changeset, username: "amir", password: "nope", dob: ~D[2000-01-01])
Or, using Keyword.validate!/2
, would be better.
You have no idea how much I agree with this @zachdaniel, and it bites too many people in the butt too frequently. Maybe it should be part of the anti-patterns documentation!?
I’ve been working with Elixir for a long time now, how come I’ve missed this gem!? I surely must have overlooked it. Thanks for sharing!
Never say never . If param2 depends on param1, I don’t see why not.
Independent optional arguments should be in keyword list for sure.
I don’t see why you’d need multiple default arguments for that? You can detect presence with keyword lists and derive defaults etc.
Keyword.put_new_lazy(opts, :key, fn ->
other_value = opts[:other_key]
some_function_of(other_value)
end)
I personally have found default arguments to be a good tool for trolling, as the default arguments are not restricted to the last arguments only as in many other languages, hence this is valid code too:
def(param1 \\ %{}, param2 \\ "", param3, param4 \\ []) do
# param3 is not optional
end
You know, it really bothers me knowing that KW lists require O(n) iteration over the list to extract the argument. I know full well that it will (almost) never matter in practice and I’m pretty sure it’s actually pushed down to native code, but I wince every time anyway…
Of course, I still use them frequently because named args can’t be beat for readability when you have a lot of them.
Totally agree. I made a whole mess by having more than one default argument (that too booleans, those are the worst)
Of course you can achieve that with keyword list. However, multiple default arguments still has its place:
Enum.min/3
def top_n(table, n, sorted_field \\ :id, sorted_fun \\ &<=/2)
Sure, there is always the caveat of not breaking existing code. But “never do it if you can avoid it” I think is always implicit in this kind of advice. I don’t see how in top_n/4
the fourth arg only makes sense with the 3rd arg. Those both look like things I’d want to be able to specify independently.
I think for libraries who have to avoid breaking changes, you should begin with options lists over even a single optional argument, so that you never have this problem. In our own application code it’s a different story as we can update all callers when we change the function. Even still, all cases where I used to reach for optional args I now instead use options lists. All of the benefits, none of the downsides.
Agreed 100%. Indeed the emphasized part IMO goes without saying. Hence my original statement didn’t include it.
Plus, @derek-zhou, more than one default argument is something that I don’t remember the last time that I needed it.
…plus you can now use treasures like Keyword.validate!
or NimbleOptions
.
There is a good chance where even your own application code becomes so large that changing all call sites would produce pull requests that touch hundreds of files spanning multiple teams.
So your advice also holds for application code I think and not just libraries.