I am trying to write a macro that defines multiple methods for a type hierarchy. What I am trying to achieve is a way to arbitrarely enumerate a type hierarchy, by defining an order()
method for each struct in the type tree.
macro enum_type(type)
let type = eval(type)
next = [type]
methods = []
counter = 0
while(!isempty(next))
let current_type = pop!(next)
children = subtypes(current_type)
map(t -> push!(next, t), children)
push!(methods, :(order(::$current_type) = $(counter += 1)))
end
end
quote
$(methods...)
end
end
end
The returned expressions do not seem to be evaluated in toplevel. Is there a way to return multiple toplevel expressions?
The desired behaviour would be to create a method for each type in the hierarchy, as an example, consider
@macroexpand @enum_type (AbstractFloat)
Should write a method order(...)
associating an arbitrary number to each type in the type tree starting from AbstractFloat. For now, the expansion of the macro with argument AbstractFloat is
quote
#= none:14 =#
var"#57#order"(::AbstractFloat) = begin
#= none:10 =#
1
end
var"#57#order"(::Float64) = begin
#= none:10 =#
2
end
var"#57#order"(::Float32) = begin
#= none:10 =#
3
end
var"#57#order"(::Float16) = begin
#= none:10 =#
4
end
var"#57#order"(::BigFloat) = begin
#= none:10 =#
5
end
end
But none of the method declaration are being evaluated.
It looks like your problem is not related to the generated expressions being top-level or not. It is rather related to the fact that you'd like to define a generic function named order
(and multiple methods associated to it), but the name order
itself is not preserved in the macro expansion: as you can see in the macro expansion you posted, order
has been replaced with var"#57order"
, which is a name that no user-defined function can actually have. This has been done to help tackle a common issue with macros, which is called hygiene.
I think the smallest modification you could make to have a macro behaving like you want would be to escape the function name (order
) in the generated expression, so that the name remains untouched:
macro enum_type(type)
let type = eval(type)
next = [type]
methods = []
counter = 0
while(!isempty(next))
let current_type = pop!(next)
children = subtypes(current_type)
map(t -> push!(next, t), children)
# see how esc is used to prevent the method name `order` from
# being "gensymmed"
push!(methods, :($(esc(:order))(::$current_type) = $(counter += 1)))
end
end
quote
$(methods...)
end
end
end
IIUC, this does what you want (and method definitions are still not top-level):
julia> @macroexpand @enum_type AbstractFloat
quote
#= REPL[1]:14 =#
order(::AbstractFloat) = begin
#= REPL[1]:10 =#
1
end
order(::Float64) = begin
#= REPL[1]:10 =#
2
end
order(::Float32) = begin
#= REPL[1]:10 =#
3
end
order(::Float16) = begin
#= REPL[1]:10 =#
4
end
order(::BigFloat) = begin
#= REPL[1]:10 =#
5
end
end
julia> @enum_type AbstractFloat
order (generic function with 5 methods)
julia> order(3.14)
2
Now, there are other things that come to mind reading your macro:
it is generally frowned upon to use eval
within the body of a macro
your let
blocks are not really needed here; I guess it would be more idiomatic to leave them out
map(f, xs)
produces an array containing all values [f(x) for x in xs]
. If f
is only used for its side effects, foreach
should be used instead. (EDIT: as noted by @CameronBieganek, append!
does precisely what's needed in this specific case)
I think that for such a task, where metaprogramming is used to generate top-level code out of (almost) nothing, rather than transforming an (user-provided) expression (possibly inside a given scope/context), I would use @eval
rather than a macro.
So I would perhaps use code such as the following:
function enum_type(type)
next = [type]
counter = 0
while(!isempty(next))
current_type = pop!(next)
append!(next, subtypes(current_type))
@eval order(::$current_type) = $(counter += 1)
end
end
which produces the same results:
julia> enum_type(AbstractFloat)
julia> order(3.14)
2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With