What you’re struggling with is called macro hygiene.
This macro would indeed expand this call:
f(:name, [option: :option]) do
data.something + 10
end
into this code:
register(:name, [option: :option], fn(data) -> data.something + 10 end)
However, the compiler recognizes that the variable data has two different providences, and treats them as different variables; it sees things more like this:
register(:name, [option: :option], fn(data1) -> data2.something + 10 end)
# ▲ ▲
# your macro's `data` ────────────────┘ │
# your user code's `data` ──────────────────────┘
Macro hygiene allows macro authors to not worry about their generated code stepping on other variables in scope, but requires more work when injecting user-provided code with your own variables.
You should be able to follow the guide linked above to decorate your macro’s data with a call to var! to escape hygiene.
Alternatively, and perhaps more idiomatically, you could allow the user code to dictate the variable name by having your macro expect clauses, similar to a case statement. Untested code snippet, that I’ve gotten to work with macros before; IIRC you have to build the AST for the fn by hand instead of using quote/unquote as there is no analog to unquote_splicing that works for clauses:
defmacro f(name, options, do: clauses) do
func_ast = {:fn, [], clauses}
quote do
register(unquote(name), unquote(options), unquote(func_ast))
end
end
end
Then in user-code:
f(:name, [option: :option]) do
data -> data.something + 10
end
This has the added benefit of letting users pattern-match and provide multiple clauses to handle different cases.
At this point though, you may well want them to just pass in a function–depending on your use-case.


















