Capturing anonymous functions in macros

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.