I would like to write a macro @unpack t
which takes an object t
and copies all its fields into local scope. For example, given
immutable Foo
i::Int
x::Float64
end
foo = Foo(42,pi)
the expression @unpack foo
should expand into
i = foo.i
x = foo.x
Unfortunately, such a macro cannot exist since it would have to know the type of the passed object. To circumvent this limitation, I introduce a type-specific macro @unpackFoo foo
with the same effect, but since I'm lazy I want the compiler to write @unpackFoo
for me. So I change the type definition to
@unpackable immutable Foo
i::Int
x::Float64
end
which should expand into
immutable Foo
i::Int
x::Float64
end
macro unpackFoo(t)
return esc(quote
i = $t.i
x = $t.x
end)
end
Writing @unpackable
is not too hard:
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
In the REPL, this definition works just fine:
julia> @unpackable immutable Foo
i::Int
x::Float64
end
julia> macroexpand(:(@unpackFoo foo))
quote
i = foo.i
x = foo.x
end
Problems arise if I put the @unpackFoo
in the same compilation unit as the @unpackable
:
julia> @eval begin
@unpackable immutable Foo
i::Int
x::Float64
end
foo = Foo(42,pi)
@unpackFoo foo
end
ERROR: UndefVarError: @unpackFoo not defined
I assume the problem is that the compiler tries to proceed as follows
@unpackable
but do not parse it.@unpackFoo
which fails because the expansion of @unpackable
has not been parsed yet. @unpackable
. This circumstance prevents @unpackable
from being used in a source file. Is there any way of telling the compiler to swap steps 2. and 3. in the above list?
The background to this question is that I'm working on an iterator-based implementation of iterative solvers in the spirit of https://gist.github.com/jiahao/9240888. Algorithms like MinRes require quite a number of variables in the corresponding state object (8 currently), and I neither want to write state.variable
every time I use a variable in e.g. the next()
function, nor do I want to copy all of them manually as this bloats up the code and is hard to maintain. In the end, this is mainly an exercise in meta-programming though.
答案 0 :(得分:1)
首先,我建议将其写成:
immutable Foo
...
end
unpackable(Foo)
其中unpackable
是一个接受类型的函数,构造适当的表达式并eval
。这有几个优点,例如您可以将它应用于任何类型,而无需在定义时修复它,并且您不必对类型声明进行大量解析(您只需调用fieldnames(Foo) == [:f, :i]
并使用它)
其次,虽然我不详细了解你的用例(并且不喜欢一揽子规则),但我会警告这种事情是不受欢迎的。它使代码更难阅读,因为它引入了非本地依赖;突然,为了知道x
是一个局部变量还是全局变量,你必须在一个完整的不同文件中查找一个类型的定义。一种更好,更通用的方法是显式解包变量,这可以通过@destruct
宏在MacroTools.jl中找到:
@destruct _.(x, i) = myfoo
# now we can use x and i
(您也可以破坏嵌套数据结构和可索引对象,这很好。)
回答你的问题:你对朱莉娅如何运行代码(s / parse / evaluate)基本上是正确的。整个块被一起解析,扩展和评估,这意味着在您的示例中,您在尝试定义之前尝试展开@unpackFoo
。
但是,在加载.jl文件时,Julia一次评估一个文件中的块,而不是一次评估所有块。
这意味着您可以愉快地编写如下文件:
macro foo()
:(println("hi"))
end
@foo()
并运行julia foo.jl
或include("foo.jl")
,它会正常运行。您不能在同一个块中使用宏定义及其用法,就像在上面的begin
块中一样。
答案 1 :(得分:1)
试着看一下Mauro的Parameters
包裹(https://github.com/mauro3/Parameters.jl)。它有一个@unpack
宏和随附的机器,类似于你的建议。