在 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