I am trying to write a macro to reduce some boilerplate. I have some builder method for structs to do some complicated transformations on a dict. I am copypasting something similar to this over and over:
struct Vehicle
engine::Engine
brand::String
function Vehicle(params::AbstractDict)
args = build(Vehicle, params)
if typeof(params) <: Exception
args
else
new(args...)
end
end
end
and I would like to just do it like this:
struct Vehicle
engine::Engine
brand::String
@build(Vehicle, params)
end
I prefer this to wrapping the whole struct on a macro (seems less explicit and well, I am not even able to write even this simple macro, so…).
I am trying to build this simpler example
macro build(type, params)
quote
function $(esc(type))($(esc(params))::AbstractDict)
args = build($(esc(type)), $(esc(params)))
$(esc(Expr(:call, :new, args...)))
end
end
end
This is the only way I found to call new. But then of course args is undefined.
Any suggestions or something obvious I am missing?
The only thing which needs escaping here is type, since that must match the name of the struct. The others, params and args, are local variables whose names don’t matter; not escaping them means that they cannot clash with e.g. type.
The other thing is that you have an args (which isn’t defined) where you want :args. Often it helps to dump what you’re trying to construct:
julia> :(new(args...)) |> dump
Expr
head: Symbol call
args: Array{Any}((2,))
1: Symbol new
2: Expr
head: Symbol ...
args: Array{Any}((1,))
1: Symbol args
julia> Expr(:call, :new, Expr(:(...), :args))
:(new(args...))
julia> macro build(type)
quote
function $(esc(type))(params::AbstractDict)
args = build($(esc(type)), params)
$(Expr(:call, :new, Expr(:(...), :args)))
end
end
end
@build (macro with 1 method)
julia> @macroexpand @build Vehicle
quote
#= REPL[26]:3 =#
function Vehicle(var"#30#params"::Main.AbstractDict)
#= REPL[26]:3 =#
#= REPL[26]:4 =#
var"#29#args" = Main.build(Vehicle, var"#30#params")
#= REPL[26]:5 =#
Main.new(var"#29#args"...)
end
end
Whether this is a good idea, rather than say making a parametric type, is another question of course.
Thanks a lot for your answer. Unfortunately, that doesn’t seem to work when used as I intend to:
struct Example
stuff::String
@build(Example, params)
end
julia> Example(Dict(:stuff => "thing"))
# UndefVarError: new not defined
Whether this is a good idea, rather than say making a parametric type, is another question of course.
Well, I am already doing it (with I think good reason), just copypasting the boilerplate constructor over and over is what I want to abstract away. Not sure how a parametric type would help.
EDIT: Simply escaping new works. Again, thanks a lot for your help
I think it’s because new() can only be called from inside a struct, and therefore needs to be escaped to be in the right context.
macro build(type, params)
quote
function $(esc(type))(params::AbstractDict)
args = build($(esc(type)), params)
$(Expr(:call, esc(:new), Expr(:(...), :args)))
end
end
end