I’m trying to write a macro which will run some code and in case it doesn’t match what I want, it will register given code with the value of the parameters in a file to be retried later on.
Here is the macro :
defmacro test_kb(name, {:=, _, [matchspec, call]} = body) do
dec_body = Macro.to_string(body)
quote do
case unquote(call) do
unquote(matchspec) -> :ok
_ ->
File.write!("#{unquote(name)}#{Timex.now |> Timex.format!("{YYYY}{0M}{0D}{m}{s}")}.txt", unquote(dec_body))
end
end
end
And an example using it :
import MacroRetry
def test_number(n) do
:ok = test_retry "random_number", 5 = :rand.uniform(n)
end
So if I do test_number(10) and it doesn’t roll 5 it should write in the file 5 = :rand.uniform(10) but instead I get 5 = :rand.uniform(n) which is normal because dec_body is determined at compile time and not run time. Is there a way to get the value of n in the macro to write it in the file ?
Depends on exactly what you mean - there are several options, and the example given doesn’t supply quite enough information to distinguish them.
the macro could access the value of n at runtime using var!. This REQUIRES that the name used in the function matches what the macro expects. This isn’t a common situation, especially with a public macro (defmacro vs defmacrop)
the caller could pass n to the macro which then uses unquote inside the quote block, and prints n separately
the macro could traverse the AST in body to find variable references and capture all of THOSE
I’m trying to achieve a macro which given some code, will run it and if it fails or doesn’t give the expected result, will register given code in a db and another service will run it later on.
Are you intending for your developers to have access to your production databases from their machines at compile time? I’m not saying it’s wrong, but it seems like an unusual requirement.
In this case I’d advocate for not using a macro and instead using a higher-order function: A function that you pass an closure (an anonymous function that keeps track of the values of all variables it needs).
Something like
def tester(function) do
case function.() do
{:ok, result} -> result
other ->
File.write!(some_location, :erlang.binary_to_term(function))
{:error, :will_be_retried_later}
end
end
Which you can pass something like
myfun = fn ->
case :rand_uniform(10) do
5 ->
{:ok, 5}
not_five ->
{:error, not_five}
end
end