在 Julia 中编写 returns 多个顶层表达式的宏
Writing a macro that returns multiple toplevel expressions in Julia
我正在尝试编写一个为类型层次结构定义多个方法的宏。我想要实现的是一种通过为类型树中的每个结构定义 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)
push!(methods, :(order(::$current_type) = $(counter += 1)))
end
end
quote
$(methods...)
end
end
end
returned 表达式似乎没有在顶层求值。有没有办法 return 多个顶层表达式?
期望的行为是为层次结构中的每个类型创建一个方法,例如,考虑
@macroexpand @enum_type (AbstractFloat)
应该编写一个方法 order(...)
将任意数字与类型树中从 AbstractFloat 开始的每个类型相关联。现在,带有参数 AbstractFloat 的宏的扩展是
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
但是正在评估方法声明的 none。
看来您的问题与生成的表达式是否为顶级无关。这与您想要定义一个名为 order
的通用函数(以及与之关联的多个方法)这一事实相当相关,但名称 order
本身并未保留在宏扩展中:如您可以在发布的宏扩展中看到,order
已被替换为 var"#57order"
,这是一个用户定义函数实际上不能拥有的名称。这样做是为了帮助解决宏的常见问题,即 hygiene.
我认为您可以对生成的表达式中的函数名称 (order
) 进行 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
IIUC,这就是你想要的(方法定义仍然不是顶级的):
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
方块在这里并不是真正需要的;我想把它们排除在外会更惯用
map(f, xs)
生成一个包含所有值 [f(x) for x in xs]
的数组。如果 f
仅用于其副作用,则 foreach
should be used instead. (EDIT: as noted by @CameronBieganek, append!
正是在这种特定情况下所需要的)
我认为对于这样的任务,元编程用于 生成 无中生有的顶级代码,而不是 转换一个(用户提供的)表达式(可能在给定的scope/context中),我会使用@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
我正在尝试编写一个为类型层次结构定义多个方法的宏。我想要实现的是一种通过为类型树中的每个结构定义 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)
push!(methods, :(order(::$current_type) = $(counter += 1)))
end
end
quote
$(methods...)
end
end
end
returned 表达式似乎没有在顶层求值。有没有办法 return 多个顶层表达式?
期望的行为是为层次结构中的每个类型创建一个方法,例如,考虑
@macroexpand @enum_type (AbstractFloat)
应该编写一个方法 order(...)
将任意数字与类型树中从 AbstractFloat 开始的每个类型相关联。现在,带有参数 AbstractFloat 的宏的扩展是
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
但是正在评估方法声明的 none。
看来您的问题与生成的表达式是否为顶级无关。这与您想要定义一个名为 order
的通用函数(以及与之关联的多个方法)这一事实相当相关,但名称 order
本身并未保留在宏扩展中:如您可以在发布的宏扩展中看到,order
已被替换为 var"#57order"
,这是一个用户定义函数实际上不能拥有的名称。这样做是为了帮助解决宏的常见问题,即 hygiene.
我认为您可以对生成的表达式中的函数名称 (order
) 进行 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
IIUC,这就是你想要的(方法定义仍然不是顶级的):
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
方块在这里并不是真正需要的;我想把它们排除在外会更惯用map(f, xs)
生成一个包含所有值[f(x) for x in xs]
的数组。如果f
仅用于其副作用,则foreach
should be used instead. (EDIT: as noted by @CameronBieganek,append!
正是在这种特定情况下所需要的)我认为对于这样的任务,元编程用于 生成 无中生有的顶级代码,而不是 转换一个(用户提供的)表达式(可能在给定的scope/context中),我会使用
@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