I’m trying to write a macro that creates new code based on the macro parameters. What I would like to create is a case statement that uses guards in the cases, where the guard comparison value is generated from the input parameters.
So the code I would like to create is something like this, for example:
case x do
x when x in [:foo, :bar] -> x
_ -> :boom
end
The issue is the guard clause. I want that little list [:foo, :bar] to be dynamically created by a macro, something like:
defmacro incredible_code(my_map) do
keys = Map.keys(my_map)
quote do
case :something do
x when x in keys -> :ok
_ -> :error
end
end
end
I didn’t really bother with the unquotes in there, I know that code does not work I can get as far as getting the usual warning about the guard having to be a compile-time list. Which is all very fine. What I am trying to do is generate that list at compile time from that map, so theoretically, that warning should not apply in this case, as far as I understand macro’s. Because macro’s generate compile-time code. Right?
Here are a couple of approaches. Neither of these are good recommendations but they are build, like your example, with the map outside the quote which means you need to convert the AST into a map again before you can use it.
defmacro incredible_code(my_map) do
# Option 1: Relies upon internal knowledge of the AST
{:%{}, _, map} = my_map_as_keyword_list
keys = Keyword.keys(my_map_as_keyword_list)
# Option 2: Will execute arbitrary code so needs to be carefully managed
{map, []} = Code.eval_quoted(my_map)
keys = Map.keys(map)
quote do
case :something do
x when x in unquote(keys) -> :ok
_ -> :error
end
end
end
A better approach would be the following using bind_quoted as Macro.escapeing the map (since the map encoding is not valid AST).
defmacro incredible_code(my_map) do
quote bind_quoted: [my_mapp: Macro.escape(my_map)] do
case :something do
x when x in unquote(Map.keys(my_map)) -> :ok
_ -> :error
end
end
end
(noting that this code also isn’t correct since its not unquoteing the other parameters as in your example).
defmacro demo(my_map, x) do
{:%{}, _, my_map_as_keyword_list} = my_map
keys = Keyword.keys(my_map_as_keyword_list)
quote do
case unquote(x) do
y when y in unquote(keys) -> :ok
_ -> :error
end
end
end
Glad it works - but I’d suggest you use my last example as being more idiomatic and less error prone. For example, your code probably won’t work with string keys.