I quickly build a small macro that does this (probably not in a very robust way) and wanted to give a small outline on how to approach such a problem For the solution look at the end.
When writing a macro a good first step is to write down what the transformation of the source code should actually be. In this case, as you noted, the desired functionality is already present for functions but also for let
blocks. So it is sufficient for the macro to transform:
@imp(; a = 2, b = a^2, c = 3)
into
let a = 2, b = a^2, c = 3
(; a = 2, b = a^2, c = 3)
end
So the next step is looking at the AST to figure out how to puzzle together the desired structure of Expr
s from the ones we recieve. For that we can use dump
(and Base.remove_linenums!
to strip away unnecessary nodes our macro won’t see but that dump
might pick up).
julia> dump(Base.remove_linenums!(quote
let a = 2, b = a^2, c = 3
(; a = 2, b = a^2, c = 3)
end
end); maxdepth=12)
Expr
head: Symbol block
args: Array{Any}((1,))
1: Expr
head: Symbol let
args: Array{Any}((2,))
1: Expr
....
To get the input of the macro, I recommend actually using a macro, e.g:
macro dump(expr)
return :(dump($(QuoteNode(expr))))
end
Then you get
julia> @dump(; a = 2, b = a^2, c = 3)
Expr
head: Symbol parameters
args: Array{Any}((3,))
1: Expr
head: Symbol kw
args: Array{Any}((2,))
1: Symbol a
2: Int64 2
....
With that it is not very hard to come up with the transformation. Here is your solution:
macro imp(expr)
expr.head == :parameters || error("I don't understand this: $(dump(expr;maxdepth=4))")
bindings = Expr(:block, (Expr(:(=), e.args[1], e.args[2]) for e in expr.args)...)
body = Expr(:tuple, expr)
return esc(Expr(:let, bindings, body))
end