【发布时间】:2016-07-28 20:19:40
【问题描述】:
我想编写一个宏@unpack t,它接受一个对象t,并将其所有字段复制到本地范围内。例如,给定
immutable Foo
i::Int
x::Float64
end
foo = Foo(42,pi)
表达式@unpack foo 应扩展为
i = foo.i
x = foo.x
不幸的是,这样的宏不能存在,因为它必须知道传递对象的类型。为了规避这个限制,我引入了一个特定类型的宏@unpackFoo foo,效果相同,但由于我很懒,我希望编译器为我编写@unpackFoo。所以我将类型定义更改为
@unpackable immutable Foo
i::Int
x::Float64
end
应该扩展成
immutable Foo
i::Int
x::Float64
end
macro unpackFoo(t)
return esc(quote
i = $t.i
x = $t.x
end)
end
写@unpackable 并不难:
macro unpackable(expr)
if expr.head != :type
error("@unpackable must be applied on a type definition")
end
name = isa(expr.args[2], Expr) ? expr.args[2].args[1] : expr.args[2]
fields = Symbol[]
for bodyexpr in expr.args[3].args
if isa(bodyexpr,Expr) && bodyexpr.head == :(::)
push!(fields,bodyexpr.args[1])
elseif isa(bodyexpr,Symbol)
push!(fields,bodyexpr)
end
end
return esc(quote
$expr
macro $(symbol("unpack"*string(name)))(t)
return esc(Expr(:block, [:($f = $t.$f) for f in $fields]...))
end
end)
end
在 REPL 中,这个定义可以正常工作:
julia> @unpackable immutable Foo
i::Int
x::Float64
end
julia> macroexpand(:(@unpackFoo foo))
quote
i = foo.i
x = foo.x
end
如果我将@unpackFoo 放在与@unpackable 相同的编译单元中,就会出现问题:
julia> @eval begin
@unpackable immutable Foo
i::Int
x::Float64
end
foo = Foo(42,pi)
@unpackFoo foo
end
ERROR: UndefVarError: @unpackFoo not defined
我认为问题是编译器尝试按如下方式进行
- 扩展
@unpackable,但不解析它。 - 尝试扩展
@unpackFoo失败,因为@unpackable的扩展尚未被解析。 - 如果我们在第 2 步没有失败,编译器现在将解析
@unpackable的扩展。
这种情况会阻止@unpackable 在源文件中使用。有没有办法告诉编译器交换上面列表中的步骤 2. 和 3.?
这个问题的背景是,我本着https://gist.github.com/jiahao/9240888 的精神,正在研究基于迭代器的迭代求解器实现。像 MinRes 这样的算法在相应的状态对象中需要相当多的变量(目前是 8 个),我不想在每次使用变量时都写 state.variable next() 函数,我也不想手动复制所有这些,因为这会使代码膨胀并且难以维护。最后,这主要是元编程的练习。
【问题讨论】:
-
我认为您可以更简单地使用生成的函数来做到这一点。但是用例是什么?
-
我不认为生成的函数会起作用,因为函数引入了一个新的变量范围并且只能在该范围内运行。我将添加一些关于问题背景的评论。
-
实际上,现在我想到了,在我的用例中,放弃状态类型并改用元组可能更合理,为此我可以通过元组获得
@unpack行为拆包(即i,x = (42,pi))。 -
@gTcV 在 Julia 中解包是通过迭代协议完成的,因此您可以通过实现
start、next和done来获得类似的行为。见docs.julialang.org/en/release-0.4/manual/interfaces -
尝试查看
Parameters包。它有一个@unpack宏和与您建议的类似的附带机制。
标签: julia