Hi Everybody,
I spent some time testing and trying different ways how to pass, process and use optional opts
in a function argument.
Ideally I’d like to settle on a single piece of code - something I can use in all my functions.
By searching I quickly realized that everybody has own take on this.
At the end I developed my own way and I’d like to kindly ask you if somebody can review it advise me if this is clever or stupid and I shouldn’t use it ;-).
defmodule Helper.ArgOpts do
@spec add_argument(map, atom, keyword, atom) :: map
def add_argument(map, map_key, keyword, keyword_key)
when is_map(map) and is_atom(map_key) and is_list(keyword) and is_atom(keyword_key) do
values =
if Keyword.has_key?(keyword, keyword_key),
do: Keyword.get_values(keyword, keyword_key),
else: []
Map.put(map, map_key, values)
end
end
defmodule MySuperApp do
@opts_default color: :red,
engine: :standard
@spec make_bike(atom, list) :: String.t()
def make_bike(brand, opts \\ []) when is_atom(brand) and is_list(opts) do
opts = @opts_default ++ opts
all_opts = Enum.into(opts, %{})
# stupid example how to use the `opts` args:
"bike: #{all_opts.color} #{brand} with #{all_opts.engine} engine"
end
@opts_default number_of_wheels: 4,
color: :red,
engine_size: :standard,
tuning?: false
@spec make_car(atom, list) :: String.t()
def make_car(brand, opts \\ []) when is_atom(brand) and is_list(opts) do
opts = @opts_default ++ opts
all_opts = Enum.into(opts, %{})
all_opts = Helper.ArgOpts.add_argument(all_opts, :features, opts, :feature)
# stupid example how to use the `opts` args:
engine =
if all_opts.tuning?,
do: "TUNED engine",
else: "#{all_opts.engine_size} engine"
"""
car: #{all_opts.color} #{brand}
with #{all_opts.number_of_wheels} wheels
and #{engine}
and features: #{inspect(all_opts.features)}
"""
end
end
IO.puts(MySuperApp.make_bike(:honda))
IO.puts(MySuperApp.make_bike(:kawasaki, engine: :fastest, color: :green))
IO.puts(MySuperApp.make_car(:ferrari))
IO.puts(MySuperApp.make_car(:lamborghini, color: :yellow, feature: :air_con, feature: :shiny_wheels))
IO.puts(MySuperApp.make_car(:hummer, color: :black, number_of_wheels: 6, tuning?: true, feature: :dark_window_tint))
Basically the idea is to have an attribute
@opts_default
with default values on top of each function that has opts
argument.
In a simple functions (like make_bike/2
) the Keyword
list with defaults + the keyword list with user specified values is converted into a Map
. Defaults get overwritten by user specified values.
In more complicated functions (like make_car/2
) it is possible to repeat a keyword (in the example feature
) and then add a features
key into the new map. New features
key holds all values (or empty list if feature
is not specified).
Can I please get feedback if this is/isn’t a good way.
Thank you ;-).
Kind regards,
Ben