在宏中嵌套eval调用是不好的做法吗?

时间:2018-12-09 11:25:57

标签: macros julia eval dsl compile-time

为说明起见,让我们说我有以下宏来计算真值表中的行:

macro bool_to_lit(a)
    eval(a) ? (x -> x) : (x -> !x)
end

macro make_clause(xs, bools, res)
    lits = map((x -> @eval @bool_to_lit $x), bools.args)
    clause_elements = map.(lits, xs.args)
    and_res = all(push!(clause_elements, res))
    return and_res
end

#@make_clause((false, false), (false, false), true) returns true

@bool_to_lit根据其自变量的值返回闭包,@make_clause使用结果来计算其自身的值。但是,由于@make_clause使用@eval,所以我的理解是它实际上在运行@bool_to_lit(因此,不仅执行语法转换)。

在这样的情况下避免使用嵌套的@eval会更好(例如,更快并生成更清晰的代码),这样整个宏树的整个结果在rutime时仅被评估一次? / p>

是否在更容易编码( ie 在使用@eval时将嵌套宏作为函数)和 vs。正确性( ie 避免嵌套的@eval时仅编译时语法转换吗?

1 个答案:

答案 0 :(得分:1)

(免责声明:我稍微缩短了代码的逻辑。这样做可能会出错,但总的观点是相同的。)

在大多数情况下,不,您不应该在宏中使用eval。您可以选择两种替代方法。首先,如果您只要求宏仅对文字布尔值(即值truefalse)起作用,则这些值将直接存储在AST中,您可以直接在编译时进行常规计算时间:

julia> macro make_clause_literal(xs, bools, res)
           clause_elements = map((lit, arg) -> lit == arg, bools.args, xs.args)
           res && all(clause_elements)
       end
@make_clause_literal (macro with 1 method)

julia> @macroexpand @make_clause_literal((false, false), (false, false), true)
true

如果输入确实是文字布尔值,则应该添加一些检查。

另一方面,如果您想很好地放入其他表达式,请将代码转换为可以执行相同操作的高效代码,并将评估留给运行时:

julia> macro make_clause(xs, bools, res)
           clause_elements = map((lit, arg) -> :($lit == $arg), bools.args, xs.args)
           esc(:($res && $(foldr((e,f) -> :($e && $f), clause_elements))))
       end
@make_clause (macro with 1 method)

julia> @macroexpand @make_clause((false, false), (false, false), true)
:(true && (false == false && false == false))

julia> @macroexpand @make_clause((false, false), (false, x), y)
:(y && (false == false && x == false))

构造&&序列应该与避免中间阵列和短路方面的问题一样好。

我建议的第三种选择是编写一个执行子句评估的普通运行时函数,并根据对它的调用重写上面的任何一个宏。我将其保留为练习。您也可以将这两种方法结合起来,并在编译时尽可能地评估表达式,但是我猜编译器已经在某种程度上做到了。