Hi, I want to use @reset in Accessors.jl to update some immutable data. The issue is that I have a “path” to the object which changes dynamically, and in my actual code it is impossible to “hard-code” this path.
A minimum working example describing my issue is below.
using Accessors
struct MyData
val::Vector{<:Real}
end
function MyData() # Default constructor
return MyData(Real[])
end
struct MyBlock
cap::Vector{<:Real}
data::Vector{<:MyData}
end
function MyBlock(
cap::Vector{<:Real},
)
return MyBlock(cap, MyData[])
end
function create_lens(item::Integer)
return @optic _[item]
end
function create_lens(item:: Symbol)
@optic getproperty(_, item)
end
function nested_lens(path::Vector{Any})
li = [create_lens(p) for p ∈ path]
lens = opcompose(li...)
return lens
end
# Example usage
some_data = MyData([1, 2, 4])
block = MyBlock(
[1, 2, 3],
[MyData(), some_data]
)
#I want to change all locations containing Vector{<:Real}. The "path" to these vectors are known.
paths_to_vectors = [
[:cap],
[:data, 2, :val]
]
# I can create "dynamic" lenses to get the values of the objects, as below
l_hard_coded1 = @optic _.cap
l1 = nested_lens(paths_to_vectors[1])
@assert all(l1(block) .== l_hard_coded1(block))
println(l1(block)) #prints [1,2,3]
l_hard_coded2 = @optic _.data[2].val
l2 = nested_lens(paths_to_vectors[2])
@assert all(l2(block) .== l_hard_coded2(block))
#I want to have a similar functionality for @reset, e.g. a "dynamic" @reset where I dont have to hard-code the "path" I want to change.
# function dynamic_reset(obj, path::Vector{<:Any}, a_lens, new_values::Vector{<:Real})
# modified_obj = obj # please help me with this code :)
# return modified_obj
# end
#desired usage:
vals2 = [500,500]
block = dynamic_reset(
block,
paths_to_vectors[2], #this path can change
l2,
vals2
)
@assert all(l2(block) .== vals2)
new_cap_val = [3,4,5]
@reset block.cap = new_cap_val #hard-coded reset works, but I want this to be "dynamic"
# Below are some options I have tested out.
# Option 1: The below replicates @reset code (https://github.com/JuliaObjects/Accessors.jl/blob/master/src/sugar.jl)
macro reset_replica(ex)
println("ex is: $(ex)")
Accessors.setmacro(identity, ex, overwrite = true)
end
@reset_replica block.cap = [4,5,6] #prints: "ex is: block.cap = [4, 5, 6]"
# # Why does the (commented-out) code below fails? If it works, I could do string manipulation to get the correct path on p2
# p2 = "block.cap = [200, 200, 300]"
# ex2 = Meta.parse(p2)
# e3 = Accessors.setmacro(identity, ex2, overwrite = true)
# eval(e3)
# Option 2: This works, but creates global variables and is not my preferred solution
function modify_object(obj, sym::Symbol, new_val)
global obj2 = obj
p = "@reset obj2.$(sym) = $(new_val)"
exp = Meta.parse(p)
eval(exp)
return obj2
end
vals_updated = [60,30]
block.data[2] = modify_object(block.data[2], :val, vals_updated)
@assert all(l2(block) .== vals_updated)