Struct 朱莉娅:如何通过修改用户提供字段中的原始不可变结构来生成新的不可变结构?

Struct 朱莉娅:如何通过修改用户提供字段中的原始不可变结构来生成新的不可变结构?,struct,julia,metaprogramming,Struct,Julia,Metaprogramming,假设我有一些不可变的结构,例如 struct Person name::Symbol age::Int end; 我想写一个函数 function copyWithModification(original_person::Person, fieldToChange::String, valueForNewField)::Person 它返回一个与旧结构相同的新Person结构,只是fieldToChange中指定的字段的值已设置为v

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

struct Person
           name::Symbol
           age::Int
       end;
我想写一个函数

function copyWithModification(original_person::Person, fieldToChange::String, valueForNewField)::Person
它返回一个与旧结构相同的新Person结构,只是fieldToChange中指定的字段的值已设置为valueForNewField。我该怎么做

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

using Setfield
function copyWithModification(original_person::Person, fieldToChange::String, valueForNewField)::Person
    return eval(Meta.parse("@set original_person." * fieldToChange * " = " * string(valueForNewField)))
end
这不起作用,因为评估是在全局范围内执行的,因此无法访问原始的_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> 

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

您不需要为此使用元编程。我认为这个正常的函数能够满足您的需要

function Person(p :: Person,fieldtochange,newvalue)
   
    newparams = [] # This array will store a new list of parameters 

    # This loop will iterate in all the fields (obtained via [fieldnames][1]) 
    # of the struct Person and compare with the given field,
    # if it coincides, adds the new value to the newparams array,
    # if not, get the values of the original person using
    # getproperty and add them to the array.

    for currentfield in fieldnames(Person) 
       if currentfield == fieldtochange
           push!(newparams,newvalue)
       else
           push!(newparams,getproperty(p,currentfield)) #[2]
       end
   end
   return Person(newparams...) #Construct a new person with the  new parameters
                               # using '...' for [splatting][3].
end
在本例中,我将函数命名为Person,使其成为另一个构造函数,但您可以将名称更改为所需的名称

[2]


[3] 您不需要为此使用元编程。我认为这个正常的函数能够满足您的需要

function Person(p :: Person,fieldtochange,newvalue)
   
    newparams = [] # This array will store a new list of parameters 

    # This loop will iterate in all the fields (obtained via [fieldnames][1]) 
    # of the struct Person and compare with the given field,
    # if it coincides, adds the new value to the newparams array,
    # if not, get the values of the original person using
    # getproperty and add them to the array.

    for currentfield in fieldnames(Person) 
       if currentfield == fieldtochange
           push!(newparams,newvalue)
       else
           push!(newparams,getproperty(p,currentfield)) #[2]
       end
   end
   return Person(newparams...) #Construct a new person with the  new parameters
                               # using '...' for [splatting][3].
end
在本例中,我将函数命名为Person,使其成为另一个构造函数,但您可以将名称更改为所需的名称

[2]


[3]

如果您不关心性能,在您的情况下,使用简单的内省是很好的,而且非常简单:

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)
为了重新获得效率,同样的技术可以通过在编译时列出字段的方式来实现。下面是一个使用以下命令的示例:

现在的性能要好得多:

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

如果您不关心性能,在您的情况下,使用简单的内省是很好的,而且非常简单:

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)
为了重新获得效率,同样的技术可以通过在编译时列出字段的方式来实现。下面是一个使用以下命令的示例:

现在的性能要好得多:

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

这种功能已经在Setfield中定义,不需要重新发明轮子

julia> using Setfield

julia> p = Person(:Smith, 10)
Person(:Smith, 10)

julia> setproperties(p, age=20)
Person(:Smith, 20)

一次可以设置多个字段,有关详细信息,请参见?设置属性。

此类功能已在设置字段中定义,无需重新设计控制盘

julia> using Setfield

julia> p = Person(:Smith, 10)
Person(:Smith, 10)

julia> setproperties(p, age=20)
Person(:Smith, 20)
一次可以设置多个字段,有关详细信息,请参阅?setproperties