# Accessors.jl - "Dynamic" version of @reset

**URL:** <https://discourse.julialang.org/t/accessors-jl-dynamic-version-of-reset/124493>\
**Category:** General Usage\
**Tags:** question, package\
**Created:** [January 7, 2025, 9:31am UTC](https://discourse.julialang.org/t/accessors-jl-dynamic-version-of-reset/124493 "2025-01-07T09:31:40Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![kampfkoloss](https://avatars.discourse-cdn.com/v4/letter/k/a183cd/32.png) [@kampfkoloss](https://discourse.julialang.org/u/kampfkoloss)\
**Post date:** [January 7, 2025, 9:31am UTC](https://discourse.julialang.org/t/accessors-jl-dynamic-version-of-reset/124493/1 "2025-01-07T09:31:40Z")

</div>

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.

```julia
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)

```

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [January 7, 2025, 11:06am UTC](https://discourse.julialang.org/t/accessors-jl-dynamic-version-of-reset/124493/2 "2025-01-07T11:06:17Z")

</div>

So by “dynamic path” I gather you mean these are static paths

```julia
julia> x = (cap=2,); x.cap
2

julia> x = (data=[0, (val=1,)],); x.data[2].val
1

```

and despite the type instability, you want a single locally scoped expression to pull off both of these:

```julia
julia> @macroexpand @reset x.cap = z
:(x = (set)(x, (identity)((Accessors.opticcompose)((PropertyLens){:cap}())), z))

julia> @macroexpand @reset x.data[2].val = z
:(x = (set)(x, (identity)((Accessors.opticcompose)((PropertyLens){:data}(), (IndexLens)((2,)), (PropertyLens){:val}())), z))

```

This seems like something the Accessors macros should support directly with `$`-interpolation, but it’s not clear what starting syntax and how the interpolation could work. EDIT: the following comment, apparently, TIL

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [January 7, 2025, 4:10pm UTC](https://discourse.julialang.org/t/accessors-jl-dynamic-version-of-reset/124493/3 "2025-01-07T16:10:03Z")

</div>

Not sure I fully understand the problem, what’s wrong with

```julia
paths = [
    (@o _.cap),
    (@o _.data[2].val)
]

p = paths[1]
@reset p(x) = 123
p = paths[2]
@reset p(y) = 456

```

?

---

<div class="post-metadata">

**Author:** ![JonasWickman](https://avatars.discourse-cdn.com/v4/letter/j/9de0a6/32.png) [@JonasWickman](https://discourse.julialang.org/u/JonasWickman)\
**Post date:** [January 7, 2025, 4:35pm UTC](https://discourse.julialang.org/t/accessors-jl-dynamic-version-of-reset/124493/4 "2025-01-07T16:35:06Z")

</div>

I’m not entirely sure I understand either, but here is a version of your `modify_object` that uses the `PropertyLens` from Accessors.jl. It’s not in place, but you can just assign to the same object:

```julia
using Accessors

function modify_object(obj, sym, new_val)
    lens = PropertyLens(sym)
    new_obj = set(obj, lens, new_val)
end

obj = (; a = 1, b = 2)
sym = :a 
new_val = 3

obj = modify_object(obj, sym, new_val)

```

See [Docstrings · Accessors.jl](https://juliaobjects.github.io/Accessors.jl/stable/docstrings/)

Note that creating a `PropertyLens` from a runtime varible is type unstable, but once the `PropertyLens` is created, using it in the `set` command is type stable. Thus, if you can avoid repeatedly creating the lens, you can use it many times without much performance penalty.
