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

问题描述 投票:0回答:5

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

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 个字段)。如何解压该结构而不花费时间并用所有这些参数使我的代码混乱?

struct julia metaprogramming
5个回答
4
投票

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

因此,当您知道

@unpack_MyModel m
时,写
@unpack a, b, c = m
就相当于写
m isa MyModel


2
投票

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

julia> struct Bar
           a
           b
       end

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

2
投票

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

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

0
投票

我认为有一种更惯用的方法,不需要宏或任何其他包。该文档建议了最惯用的方法,例如矩阵分解的文档。

特别是这一行:

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

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

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

你的结构看起来像这样

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

要实现迭代接口,您需要定义

Base.iterate
,它将
MyModel
作为参数和“状态”。该函数返回与状态相对应的元素,并调用下一次迭代(有点像链表)。例如:

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
时停止,这就是为什么当没有什么可以迭代时必须返回
nothing

高级示例

您可以在 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 个字段的结构的特殊情况下,我想不出更符合人体工程学的方式,也许数据更适合另一种存储选项。


0
投票

我刚刚开始学习 Julia,我正在阅读此网页 https://www.matecdev.com/posts/julia-structs.html

在“解构结构”标题下,它的代码类似于:

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 = MyModel()
(; a, b, c) = m
println(a)
© www.soinside.com 2019 - 2024. All rights reserved.