neuone
Problem
I have a module that does a lookup and I structured in a way where you can pass either 1, 2 or 3 arguments.
defmodule FooExampleOne do
def lookup(id) do
IO.inspect("Lookup 1: #{id}")
end
def lookup(id, place_id) do
IO.inspect("Lookup 2: #{id} #{place_id}")
end
def lookup(id, place_id, company_id) when company_id in [:internal, :external] do
IO.inspect("Lookup 3: #{id} #{place_id} #{company_id}")
end
end
Adding a default date as another argument
I then got a requirement to provide a date as another argument. The user can always pass in another date but by default it would be today.
defmodule FooExampleTwo do
@default DateTime.utc_now()
def lookup(id, date \\ @default) do
IO.inspect("Lookup 1: #{id} #{date}")
end
def lookup(id, place_id, date \\ @default) do
IO.inspect("Lookup 2: #{id} #{place_id} #{date}")
end
def lookup(id, place_id, company_id, date \\ @default) when company_id in [:internal, :external] do
IO.inspect("Lookup 3: #{id} #{place_id} #{company_id} #{date}")
end
end
Compile Error
This won’t work as I will get a (CompileError) def lookup/3 defaults conflicts with lookup/2. I looked at the docs and I saw this and then came up with this solution and it works with some changes.
defmodule FooExampleThree do
@default DateTime.utc_now()
def lookup(params, date \\ @default)
def lookup({id}, date) do
IO.inspect("Lookup 1: #{id} #{date}")
end
def lookup({id, place_id}, date) do
IO.inspect("Lookup 2: #{id} #{place_id} #{date}")
end
def lookup({id, place_id, company_id}, date) when company_id in [:internal, :external] do
IO.inspect("Lookup 3: #{id} #{place_id} #{company_id} #{date}")
end
end
Question About My Approach
I added a function head that declares the default and then put my arguments in a tuple. Is this style ok?
Part of me doubts that this solution is ideal. If it’s not a tuple I can alway pass in a map in the params position.
Any feedback or thoughts around the approach?
Trending in Questions
Other Trending 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
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #ai
- #phoenix_html
- #elixirconf-us
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
lud
The general way of doing this is to declare a single head with all the optional arguments, and then a single function with the maximum number of arguments
Note that when you do this:
It is evaluated at compile time. So the default date will be the date at the moment of compilation, which is probably not what you want.
But you can use function calls as default arguments:
Because this:
Compiles as the same as this:
Edit:
If you regenerate Erlang code from this module:
You get a bunch of attribute and these function definitions:
al2o3cr
The problem you ran into was that
lookup/2(with a date explicitly supplied) isn’t distinguishable fromlookup/2withplace_idand the date defaulted.The tuple provides a way to distinguish the two - one has
{id}, dateand the other has{id, place_id}- but it does seem kind of odd. A keyword list (lookup(id, place_id: ... company_id: ..., etc)) would be more idomatic.One important callout: defining
@defaultlike your examples will set the default to the date when the module is compiled!DateTime.utc_nowwill run exactly once…The behavior is different if the code is written out in the
\\clause:Note that
"in @default"prints before the compiled module is inspected.neuone
Good catch!
The Head
I took the code below and threw it in IEX
In my example the 2nd and 3rd arguments can be nil.
and it generated a bunch of these definitions as per your Erlang example
Now if I write out lookup(“A”) I will get back “A” and a Date.
But I want to control where that appears in the module.
I don’t know if I’m making sense here.
Part of me is thinking maybe I need to rewrite this module and and be more explicit like this:
neuone
Thank you for the example
I like idiomatic. Does the order of the keyword matter? Are these two examples considered the same or different.
cmo
The keyword order won’t matter if you’re using
fetchorgetto retrieve them and you’re not using/allowing/expecting duplicate keys.lud
Hmmm not sure I understand.
If you pattern match on the keyword list then the list has to be passed in the exact same order with the exact same number of items.
These two are equivalent:
So as @cmo says you can use
Keyword.fetch/2in the function body instead.You can pattern match on a map in any order though, also ignoring extra keys.
Otherwise you do not have to use default arguments, you can write equivalent of the generated functions by hand by providing all the clauses. But yeah if you want to accept nil then a keyword or map would be more suited.
Now, are you sure you will use all those different combinations? If not, then maybe using functions like
lookup_by_id_and_place_and_companyis more direct, indeed. Only write code that you will use.neuone
I have been doing some context switching between Elixir and Swift. And my brain is thinking in Swift too much when I wrote that.
In the Swift language you can write something like this:
and then you have functions signatures like this
I am basically trying to replicate this Swift code in the Elixir idiomatic way.
lud
Well you can pattern match the arguments and use guards
Not very clear to read but it lets you add the extra argument.
I would rather use a map for pattern matching on 1, 2 and 3 keys, but that would just be a more explicit version of your first tuple solution.
Can you really not pass the id and a keyword list like it was suggested?
dimitarvp
Even though this thread was likely very educational for you I’d still suggest you go with this. Be explicit unless you really truly need that flexibility in your own code, which I’m reasonably certain you don’t (meaning you can write it in a way to use the explicitly named functions).
neuone
I agree with you not very clear to read but still an interesting approach with the guards.
I’m sure I could, I just wanted to collect some feedback.