Get the value of a parameter at runtime in a macro

Hello,

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 ?

Thanks in advance for your time,

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

Some calls to consider:

:ok = test_retry "pinned thing", %{a: ^n} = frob_universe()

:ok = test_retry "binary size", <<"foo", rest::binary-size(n)>> = "foobar"
1 Like

What are you trying to achieve with such a macro? Maybe there is a much simpler way to get to your real goal.

1 Like

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.

Oh no the code will run on a remote server and the database will be a local dets I think

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
1 Like