如何将 Julia 结构完全解压到局部变量中?

How to unpack a Julia struct entirely into local variables?

我创建了一个包含大量参数的复杂计算模型。由于我需要 运行 很多场景,我决定将所有这些输入参数包装成一个巨大的 struct:

using Parameters
@with_kw struct MyModel
    a::Int = 5
    b::Float64 = 5.5
    c::Matrix{Float64} = rand(3,4)
    # 40 other parameters go here
end

我有一个对象 m 例如:

m = MyModel(a=15)

现在写数学代码的时候不想在每个符号前写m.。因此我需要将结构字段变成局部变量。一种方法是使用 @unpack 宏:

@unpack a, b, c = m

对于我想在各种函数中解包的大型结构,这很不方便(请注意,我的结构有大约 40 个字段)。我怎样才能在不花时间和用所有这些参数弄乱我的代码的情况下解压结构?

对于自定义案例,您可以为您的类型制作专用宏,对于其他案例,请参见上面的答案:

macro unpack_MyModel(q)
    code =  Expr(:block, [ :($field = $q.$field) for field in fieldnames(MyModel) ]...)
    esc(code)
end

这只是插入以下代码:

julia> @macroexpand @unpack_MyModel(m)
quote
    a = m.a
    b = m.b
    c = m.c
end

这个宏可以在任何函数内部使用,例如:

function f(m::MyModel)
    @unpack_MyModel(m)
    return a+b
end

来自 Parameters.jl 的宏 @with_kw 为此定义了一个宏:

julia> using Parameters

julia> @with_kw struct MyModel  # exactly as in the question
           a::Int = 5
           b::Float64 = 5.5
           c::Matrix{Float64} = rand(3,4)
           # 40 other parameters go here
       end
MyModel

julia> @macroexpand @unpack_MyModel x
quote
    a = x.a
    b = x.b
    c = x.c
end

因此当您知道 m isa MyModel.

时,写 @unpack_MyModel m 等同于写 @unpack a, b, c = m

另一种选择是StaticModules.jl。这是我从该包的自述文件中复制并粘贴的示例:

julia> struct Bar
           a
           b
       end

julia> @with Bar(1, 2) begin
           a^2, b^2
       end
(1, 4)

我认为有一种更惯用的方法不需要宏或任何其他包。该文档建议使用最惯用的方法,例如 Matrix factorization.

的文档

特别是这一行:

julia> l, u, p = lu(A); # destructuring via iteration

这表明迭代是正确的方法。所以剩下的就是让你的结构实现 iteration interface

一个非常简单但不惯用的例子:

你的结构看起来像这样

struct MyModel
    a::Int = 5
    b::Float64 = 5.5
    c::Matrix{Float64} = rand(3,4)
    # 40 other parameters go here
end

要实现迭代接口,您需要定义 Base.iterate,它将 MyModel 作为参数和“状态”。该函数 returns 对应于状态的元素,并调用下一次迭代(有点像链表)。例如:

function Base.iterate(m::MyModel, state)
    if state == 1
        return(m.a, state+1)
    elseif state == 2
        return(m.b, state+1
    elseif state == 3
        return(m.c, state+1)
    else
        return nothing
    end
end

在 julia 中,迭代在 next(iter) == nothing 时停止,这就是为什么你必须 return nothing 当没有什么可以迭代时。

高级示例

您可以在 source code 中为 lu 因式分解找到一个更加惯用(但做作)的示例:

# iteration for destructuring into components
Base.iterate(S::LU) = (S.L, Val(:U))
Base.iterate(S::LU, ::Val{:U}) = (S.U, Val(:p))
Base.iterate(S::LU, ::Val{:p}) = (S.p, Val(:done))
Base.iterate(S::LU, ::Val{:done}) = nothing

如果我没记错的话,它使用 Val 进行一些编译时优化。

在具有 40 个字段的结构的特定情况下,我想不出更符合人体工程学的方式,也许数据更适合其他存储选项。