struct - Julia:如何通过修改用户提供的字段中的原始不可变结构来生成新的不可变结构?

标签 struct julia metaprogramming

假设我有一些不可变的结构,例如

struct Person
           name::Symbol
           age::Int
       end;

我想写一个函数

function copyWithModification(original_person::Person, fieldToChange::String, valueForNewField)::Person

它返回一个新的 Person 结构,就像旧的结构一样,除了 fieldToChange 中指定的字段的值已设置为 valueForNewField。我该怎么做?

我目前的尝试使用 Setfield和元编程

using Setfield
function copyWithModification(original_person::Person, fieldToChange::String, valueForNewField)::Person
    return eval(Meta.parse("@set original_person." * fieldToChange * " = " * string(valueForNewField)))
end

这不起作用,因为 eval 是在全局范围内执行的,因此无法访问 original_person 对象:

julia> struct Person
                  name::Symbol
                  age::Int
              end;

julia> using Setfield

julia> function copyWithModification(original_person::Person, fieldToChange::String, valueForNewField)::Person
           return eval(Meta.parse("@set original_person." * fieldToChange * " = " * string(valueForNewField)))
       end
copyWithModification (generic function with 1 method)

julia> person_local_scope = Person(:test, 10)
Person(:test, 10)

julia> copyWithModification(person_local_scope, "age", 20)
ERROR: UndefVarError: original_person not defined
Stacktrace:
 [1] top-level scope at /Users/lionstarr/.julia/packages/Setfield/XM37G/src/sugar.jl:182
 [2] eval at ./boot.jl:330 [inlined]
 [3] eval(::Expr) at ./client.jl:425
 [4] copyWithModification(::Person, ::String, ::Int64) at ./REPL[3]:2
 [5] top-level scope at REPL[5]:1

julia> 

我应该指出我不关心这段代码的性能;它只会被调用一次或两次。关键是要保存代码复制和人为错误,因为我实际想要使用此代码的结构要大得多。

最佳答案

如果您不关心性能,在您的情况下使用普通内省(introspection)就可以了而且非常简单:

function copy_with_modification1(original::T, field_to_change, new_value) where {T}
    val(field) = field==field_to_change ? new_value : getfield(original, field)
    T(val.(fieldnames(T))...)
end

例如,它会产生以下结果:

julia> struct Person
           name::Symbol
           age::Int
       end

julia> p = Person(:Joe, 42)
Person(:Joe, 42)

julia> using BenchmarkTools
julia> @btime copy_with_modification1($p, :age, 43)
  666.924 ns (7 allocations: 272 bytes)
Person(:Joe, 43)

为了重新获得效率,可以通过在编译时列出字段的方式实现相同类型的技术。这是一个使用 generated function 的示例:

# Can't have closures inside generated functions, so the helper function
# is declared outside
function val_(original, field, field_to_change, new_value)
    field == field_to_change ? new_value : getfield(original, field)
end

@generated function copy_with_modification2(original, field_to_change, new_value)
    # This is the "compile-time" part
    T = original           # here `original` refers to the type of the argument
    fields = fieldnames(T) # fieldnames is called compile-time

    # This is the "run-time" part
    quote
        # We broadcast only over `fields`, other arguments are treated as scalars
        $T(val_.(Ref(original), $fields, Ref(field_to_change), Ref(new_value))...)
    end
end

性能现在好多了:

julia> @btime copy_with_modification2($p, :age, 43)
  2.533 ns (0 allocations: 0 bytes)
Person(:Joe, 43)

关于struct - Julia:如何通过修改用户提供的字段中的原始不可变结构来生成新的不可变结构?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63198202/

相关文章:

c++ - 在 C++ 中取消引用指向结构的指针

Julia DataFrame 输出函数

types - 方法错误: `convert` when using types defined in modules in separate files

julia - Flux.jl 中的交叉熵损失 - Julia

c - 我的代码不起作用。我的 while 循环有问题

C 段错误双指针赋值位于结构内部而不是外部。

c++ - 我可以使用聚合初始值设定项来返回 C++ 中的结构吗?

c++ - CPP 元编程 : contains function for tuples

c++ - 对 Singleton 类的静态成员函数的 undefined reference

c++ - 有没有办法将 lambda 的签名推断为 mpl 序列?