在 Julia 中编写一个返回多个顶级表达式的宏

MPa*_*aga 6 metaprogramming 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
Run Code Online (Sandbox Code Playgroud)

返回的表达式似乎不在顶层计算。有没有办法返回多个顶级表达式?

所需的行为是为层次结构中的每种类型创建一个方法,例如,考虑

@macroexpand @enum_type (AbstractFloat)
Run Code Online (Sandbox Code Playgroud)

应该编写一个方法,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
Run Code Online (Sandbox Code Playgroud)

但是没有对方法声明进行评估。

ffe*_*tte 5

看起来您的问题与生成的表达式是否为顶级无关。这与您想定义一个名为order(以及与其关联的多个方法)的通用函数这一事实相当相关,但名称order本身并未保留在宏扩展中:正如您在发布的宏扩展中所见,order已被替换为var"#57order",这是一个用户定义函数实际上不能拥有的名称。这样做是为了帮助解决宏的一个常见问题,称为卫生

我认为您可以对宏进行的最小修改是在生成的表达式中对函数名称 ( )进行转义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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)




现在,在阅读您的宏时,您会想到其他一些事情:

  • 通常不赞成在eval宏的主体内使用

  • let这里并不真正需要你的积木;我想把它们排除在外会更惯用

  • map(f, xs)生成一个包含所有值的数组[f(x) for x in xs]。如果f仅用于其副作用,foreach则应改用。(编辑:正如@CameronBieganek 所指出的,append!正是在这种特定情况下所需要的)

  • 我认为对于这样的任务,元编程用于从(几乎)什么都没有生成顶级代码,而不是转换(用户提供的)表达式(可能在给定的范围/上下文内),我会使用@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
Run Code Online (Sandbox Code Playgroud)

产生相同的结果:

julia> enum_type(AbstractFloat)

julia> order(3.14)
2
Run Code Online (Sandbox Code Playgroud)

  • 比“foreach”更简单的是“append!(next, subtypes(current_type))”。 (2认同)