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

6

我正在尝试编写一个宏来为类型层次结构定义多个方法。我的目标是以任意顺序枚举类型层次结构,通过为类型树中的每个结构定义一个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

返回的表达式似乎不是在顶层中被评估的。有没有一种方法可以返回多个顶层表达式?

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

@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

但是没有任何方法声明被评估。


你能添加一个 MWE 来展示为什么它们不是顶层的吗?(我的意思是它们不是,但在这种情况下,从我所看到的来说,这不应该是一个问题。你没有定义任何只有顶层的东西) - Frames Catherine White
提醒一下,你的 @enum 宏会遮蔽来自 Base 的 @enum 宏。 - Cameron Bieganek
回应 @LyndonWhite ,您能否添加一个示例以显示所需的行为? - Cameron Bieganek
添加了一些更多的细节。 - MPaga
1个回答

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

如果我理解正确,这个代码可以满足你的需求(同时方法定义仍然不是顶层):

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。(编辑:如@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

产生相同结果的代码:

julia> enum_type(AbstractFloat)

julia> order(3.14)
2

2
foreach更简单的方法是append!(next, subtypes(current_type)) - Cameron Bieganek
1
我编辑了帖子,包括这个简化版。谢谢! - François Févotte

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接