Macros: accessing function args from generated code in the function body

Hi,

I’m trying to build a toy test library as an exercise in using macros. I’ve managed to get most things I tried doing to work eventually, but I’ve hit a brick wall with this one.

Basically I want to be able to write this code (I’ve tried to strip it down to be as simple and generic as possible):

outer do
  inner(1)
  inner(2)
end

… and have it expand to:

def my_method(arg) do
  IO.puts(arg + 1)
  IO.puts(arg + 2)
end

Here are the basic macros I have:

defmacro outer(do: body) do
  quote do
    def my_method(arg) do
      unquote(body)
    end
  end
end

defmacro inner(n) do
  quote do
    IO.puts(arg + unquote(n))
  end
end

This fails to compile, with the error undefined function arg/0. I’m assuming this is some kind of hygiene issue, but none of the things I’ve tried have resulted in being able to generate code in the function body separately, while still being able to access the function’s arguments.

I’m fairly sure there’s a simple solution to this – can anyone point me in the right direction ?

Thanks!

You can use var!/2 to access variables from the outer scope.

But please keep in mind, that you then make those variables part of the contract, in my opinion, its easier to understand and document to just take that extra argument in the macro.

2 Likes

Could you expand on where/how I should use var!? I’d already tried doing this:

defmacro outer(do: body) do
  quote do
    def my_method(arg) do
      var!(arg)
      unquote(body)
    end
  end
end

… or this …

defmacro inner(n) do
  quote do
    IO.puts(var!(arg) + unquote(n))
  end
end

… and in both cases I got expected "arg" to expand to an existing variable or be part of a match.

1 Like

Well, where do you use inner/1? The only place I see where arg is defined, doesn’t call inner/1.

inner/1 is used by the code that calls the macros, inside the body of the call to outer/1:

outer do
  inner(1)
  inner(2)
end

I want outer/1 to define a function that takes an argument, then multiple calls to inner/1 to generate code inside the body of that function, that use the value of the argument.

I realise that hiding the argument may not be a great idea, but in the real code where I’m trying to do the same thing it will remove a lot of boilerplate (and as I said it’s only really an experiment at this stage).