在宏中嵌套 eval 调用是不好的做法吗?
Is nesting eval calls in macros bad practice?
为了说明,假设我有以下宏计算真实的行table:
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
returns 一个闭包取决于它的参数值,@make_clause
使用结果来计算它自己的值。但是,由于 @make_clause
使用 @eval
,我的理解是它实际上运行 @bool_to_lit
(因此不只是执行句法转换)。
在这种情况下避免使用嵌套 @eval
是否会更好(如更快和生成更清晰的代码),这样整个宏树的整个结果在运行时只计算一次?
它是否是更容易编码(即 在使用 @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))
构建一个 &&
的序列应该尽可能避免中间数组和短路。
我推荐的第三种选择是编写一个正常的运行时函数来执行子句评估,并根据对其的调用重写上述任一宏。我把它留作练习。您也可以结合使用这两种方法并尽可能在编译时评估表达式,但我猜编译器已经在某种程度上这样做了。
为了说明,假设我有以下宏计算真实的行table:
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
returns 一个闭包取决于它的参数值,@make_clause
使用结果来计算它自己的值。但是,由于 @make_clause
使用 @eval
,我的理解是它实际上运行 @bool_to_lit
(因此不只是执行句法转换)。
在这种情况下避免使用嵌套 @eval
是否会更好(如更快和生成更清晰的代码),这样整个宏树的整个结果在运行时只计算一次?
它是否是更容易编码(即 在使用 @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))
构建一个 &&
的序列应该尽可能避免中间数组和短路。
我推荐的第三种选择是编写一个正常的运行时函数来执行子句评估,并根据对其的调用重写上述任一宏。我把它留作练习。您也可以结合使用这两种方法并尽可能在编译时评估表达式,但我猜编译器已经在某种程度上这样做了。