Reputation: 121000
I need to dynamically generate function clauses, basing on the user’s config. For the sake of clarity, imagine I have a list of atoms:
@atoms ~w|foo bar baz|a
Coming from, say, config.exs
. What I need is to generate this function (the MCVE is oversimplified, but it gives an impression on what I actually need):
@checker fn
{:foo, _} -> false
{:bar, _} -> false
{:baz, _} -> false
_ -> true
end
What I am currently doing is:
@clauses Enum.map(@atoms, fn tag ->
{:->, [], [[{:{}, [], [tag, {:_, [], Elixir}]}], false]}
end) ++ [{:->, [], [[{:_, [], Elixir}], true]}]
defmacrop checker, do: {:fn, [], @clauses}
It works pretty fine, but I expect I am overcomplicating things, missing something simple. So, my question is:
Is there an easy way to generate the function clauses in compile time?
Upvotes: 1
Views: 150
Reputation: 222118
I made it somewhat (see below for more) more readable using quote
:
defmodule A do
@atoms ~w|foo bar baz|a
@clauses Enum.flat_map(@atoms, fn tag ->
quote do: ({unquote(tag), _} -> false)
end) ++ quote(do: (_ -> true))
defmacro checker, do: {:fn, [], @clauses}
end
defmodule B do
require A
f = A.checker
IO.inspect f.({:foo, :ok})
IO.inspect f.({:bar, :ok})
IO.inspect f.({:baz, :ok})
IO.inspect f.({:quux, :ok})
end
Output:
false
false
false
true
I expected quote(do: a -> b)
to work, but it's a parse error right now so we have to do quote(do: (a -> b))
which wraps the quoted fragment we want in a list.
I also expected unquote
to work inside fn
when it's inside quote
, but that also doesn't.
iex(1)> quote do
...(1)> fn
...(1)> unquote()
...(1)> _ -> true
...(1)> end
...(1)> end
** (SyntaxError) iex:2: expected clauses to be defined with -> inside: 'fn'
I believe these two are either bugs or missing features.
Upvotes: 2