You can totally manipulate an AST using functions. After all, an Abstract Syntax Tree in Elixir are just three-argument tuples*.
(* bar a few literals, see iex> h quote
for details)
But if you want to grab something from an external context, or apply something to an external context, you need to use a Macro. A Macro will, instead of normal parameters, receive them in AST-format, so you can manipulate them before evaluating them (if at all).
Your example goes wrong for another reason than for using a function instead of a macro. Any kind of AST (using e.g. an AST-representation as in your example) is, by default, hygienic. This means that it does not have access to things defined in the outer scope.
The simplest way to bring an outer variable inside the scope, that is often most appropriate in these kinds of situations, is to use some unquote
statement inside a quote
This will first replace the variable named in the unquote with its value, and then the outer quote will use its value instead. An example would be:
def Foo
defmacro test(x)
quote do
"%The result of #{Macro.to_string(x)} is #{unquote(x)}"
end
end
If you actually want to grab the value of a variable in the external context in a macro, you can use var!
. Do note that while this can be convenient in some cases, it does break the principle of least surprise and functional purity, because you are now writing code that depends on something outside of its own definition.
To answer your question:
- There is no difference in scope between the parameters of a function and its body.
- There is a difference in scope between quoted code inside a macro and code outside of this quote-block.
- There is a difference in scope between the inside of a macro, and the location it is used in, but a macro can cheat by using
var!
to access things outside of itself.
As to why your example above doesn’t work:
The AST you’re presenting is not the AST of a local variable i
. Instead, it is the AST describing an identifier i
in the context of the module Fun
(i.e. outside of the function definition of test
) which might resolve to a variable or to a zero-arity function (In this case, it cannot find either, so it raises an error). If you want to refer to a local variable, you should use {:i, [], nil}
. Or, even better would be to just write the code using quote do ... end
, which would be a lot more readable.