I want to be able to define a variable that represents a component of a structure and then use this variable to set or get the structure component. One thought I have is to do something like:
julia> mutable struct A
a1; a2
end
julia> a=A((1,2),3)
julia> var = :a2
:a2
julia> getproperty(a, var)
3
So far so good. But if I try to do the same thing for an array component I have a problem:
julia> var = :(a1[1])
:(a1[1])
julia> getproperty(a, var)
ERROR: MethodError: no method matching getproperty(::A, ::Expr)
Closest candidates are:
getproperty(::Any, ::Symbol) at Base.jl:38
getproperty(::Any, ::Symbol, ::Symbol) at Base.jl:50
Stacktrace:
[1] top-level scope
@ REPL[14]:1
My question is what is the simplest way to do what I want? Any solution would also have to work with nested structures.
I’m not sure if I can help with your need, but I can just point out that the source of the error is that :(a1[1]) is a different syntactic construct altogether:
julia> :a |> typeof
Symbol
julia> :(a) |> typeof
Symbol
julia> :(a[1]) |> typeof
Expr
The most obvious approach would be to turn your expression into a lambda function. This may require some metaprogramming.
julia> mutable struct A
a1; a2
end
julia> x = A([1,2,3],[4,5,6])
A([1, 2, 3], [4, 5, 6])
julia> var = A->A.a2[2]
#3 (generic function with 1 method)
julia> x |> var
5
using Accessors
var = @optic _.a2 # variable that represents a component of a structure
var(a) # get the component
new_a = set(a, var, 123) # return object like a, but with new_a.a2 set to 123
Array components, and basically anything else is supported with the same syntax, including arbitrary combinations: try @optic _[1], @optic first(_.a2) etc.
Out of the box, Accessors only directly support immutable modifications. You can still use these lenses as “variables that represents a component of a structure”, but implement mutations yourself.
But first, are you sure you need actual mutability here? Often, it’s more efficient to use immutable structs and create new instances with modified values, as Accessors do.
Yes this is needed since there will be multiple variables that will point to the same memory. So, in my original example, if the set b = a is done after a is defined, b should change if a changes.