Julia macro expansion order

时间:2016-07-28 20:19:41

标签: julia

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

  1. Expand @unpackable but do not parse it.
  2. Try to expand @unpackFoo which fails because the expansion of @unpackable has not been parsed yet.
  3. If we wouldn't fail already at step 2, the compiler would now parse the expansion of @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.

2 个答案:

答案 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.jlinclude("foo.jl"),它会正常运行。您不能在同一个块中使用宏定义及其用法,就像在上面的begin块中一样。

答案 1 :(得分:1)

试着看一下Mauro的Parameters包裹(https://github.com/mauro3/Parameters.jl)。它有一个@unpack宏和随附的机器,类似于你的建议。