为了举例说明,假设我有以下宏计算真值表中的行:
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只评估一次,这会更好吗(比如在更快和生成更干净的代码中(?
这是更容易编码(,即使用@eval
时将嵌套宏视为函数(与正确性(即在避免嵌套@eval
时仅进行编译时语法转换(?
(免责声明:我稍微缩短了代码的逻辑。也许我在做这件事时出错了,但总的来说是一样的。(
在大多数情况下,不应该在宏中使用eval
。除了你所提供的,还有两种可能的选择。首先,如果您只需要宏处理文本布尔值(即值true
和false
(,那么这些值就直接存储在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))
在避免中间阵列和短路方面,构造&&
序列应该尽可能好。
我建议的第三种选择是编写一个正常的运行时函数来进行子句求值,并根据对它的调用重写上面的任何一个宏。我把它留作练习。您也可以将这两种方法结合起来,并尽可能在编译时评估表达式,但我想编译器在某种程度上已经做到了。