我一直在弄乱 Julia 中的生成函数,并遇到了一个我不完全理解的奇怪问题:我的最终目标是从内部调用宏(更具体地说是 @tullio
)生成的函数(执行一些取决于输入张量的张量收缩)。但我一直遇到问题,我将问题范围缩小到从生成的函数中调用宏。
为了说明问题,让我们考虑一个也失败的非常简单的示例:
macro my_add(a,b)
return :($a + $b)
end
function add_one_expr(x::T) where T
y = one(T)
return :( @my_add($x,$y) )
end
@generated function add_one_gen(x::T) where T
y = one(T)
return :( @my_add($x,$y) )
end
通过这些声明,我发现 eval(add_one_expr(2.0))
按预期工作并返回和表达式
:(@my_add 2.0 1.0)
正确计算结果为3.0
。
但是评估 add_one_gen(2.0)
会返回以下错误:
MethodError: no method matching +(::Type{Float64}, ::Float64)
做了一些研究,我发现@ generated
实际上生成了两个代码,并且在一个代码中只能使用变量的类型。我认为这就是这里正在发生的事情,但我根本不明白发生了什么。这一定是宏和生成函数之间有一些奇怪的交互。
有人可以解释和/或提出解决方案吗?谢谢!
最佳答案
我发现将生成的函数视为具有两个组件是有帮助的:主体和任何生成的代码(quote..end
)。主体在编译时进行评估,并且不“知道”值,只“知道”类型。因此,对于以 x::T
作为参数的生成函数,主体中对 x
的任何引用实际上都指向类型 T
。这可能会非常令人困惑。为了让事情更清楚,我建议正文仅引用类型,而不是值。
这是一个小例子:
julia> @generated function show_val_and_type(x::T) where {T}
quote
println("x is ", x)
println("\$x is ", $x)
println("T is ", T)
println("\$T is ", $T)
end
end
show_val_and_type
julia> show_val_and_type(3)
x is 3
$x is Int64
T is Int64
$T is Int64
插值的 $x
表示“从主体(指的是 T
)中取出 x
并将其拼接进去。
如果您遵循从不引用正文中的值的方法,则可以通过删除 @ generated
来测试生成的函数,如下所示:
julia> function add_one_gen(x::T) where T
y = one(T)
quote
@my_add(x,$y)
end
end
add_one_gen
julia> add_one_gen(3)
quote
#= REPL[42]:4 =#
#= REPL[42]:4 =# @my_add x 1
end
这看起来很合理,但是当我们测试它时,我们得到
julia> add_one_gen(3)
ERROR: UndefVarError: x not defined
Stacktrace:
[1] macro expansion
@ ./REPL[48]:4 [inlined]
[2] add_one_gen(x::Int64)
@ Main ./REPL[48]:1
[3] top-level scope
@ REPL[49]:1
让我们看看这个宏给我们带来了什么
julia> @macroexpand @my_add x 1
:(Main.x + 1)
它指向 Main.x
,但它不存在。宏太急切了,我们需要延迟它的评估。标准方法是使用 esc
。最后,这有效:
julia> macro my_add(a,b)
return :($(esc(a)) + $(esc(b)))
end
@my_add
julia> @generated function add_one_gen(x::T) where T
y = one(T)
quote
@my_add(x,$y)
end
end
add_one_gen
julia> add_one_gen(3)
4
关于macros - 从 Julia 生成的函数中调用宏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72830506/