Похоже, что ваша проблема не связана с тем, что сгенерированные выражения находятся на верхнем уровне или нет. Это скорее связано с тем, что вы хотите определить обобщенную функцию c с именем order
(и несколько связанных с ней методов), но само имя order
не сохраняется в расширении макроса: как вы вы можете увидеть в размещенном вами расширении макроса, order
был заменен на var"#57order"
, то есть имя, которое не может иметь ни одна пользовательская функция. Это было сделано, чтобы помочь решить общую проблему с макросами, которая называется гигиена .
Я думаю, что самая маленькая модификация, которую вы могли бы сделать, чтобы макрос работал так, как вы хотите, была бы escape имя функции (order
) в сгенерированном выражении, так что имя остается неизменным:
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
IIU C, это делает то, что вы хотите (и определения метода все еще не на высшем уровне):
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
Теперь, читая ваш макрос, приходят на ум другие вещи:
обычно не одобряется использование eval
в теле макроса
ваши let
блоки здесь на самом деле не нужны; Я предполагаю, что было бы более идиоматическим c опустить их
map(f, xs)
создает массив, содержащий все значения [f(x) for x in xs]
. Если f
используется только для побочных эффектов, вместо него следует использовать foreach
. (РЕДАКТИРОВАТЬ: как отмечает @CameronBieganek, append!
делает именно то, что нужно в этом конкретном случае c)
Я думаю, что для такой задачи, где метапрограммирование используется для генерации кода верхнего уровня из (почти) ничего, вместо преобразования (предоставленного пользователем) выражения (возможно, в заданную область / контекст), I будет использовать @eval
вместо макроса.
Так что я бы, возможно, использовал такой код:
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
, который производит те же результаты:
julia> enum_type(AbstractFloat)
julia> order(3.14)
2