How to represent struct component

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

Here is some potentially relevant docs, but fwiw it feels like what you’re trying to do is a bit unconventional, maybe if you expand of why you want to do it someone could point out the conventional way if doing it.
https://docs.julialang.org/en/v1/manual/metaprogramming/
https://docs.julialang.org/en/v1/base/base/#Core.Symbol
https://docs.julialang.org/en/v1/base/base/#Core.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

https://docs.julialang.org/en/v1/manual/metaprogramming/

1 Like

Yes this is exactly what I wanted! Much Thanks.

That’s exactly what lenses are!

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.

1 Like

@aplavin This is cool. But I don’t see how to set a value without making a copy. Setting without copying is needed for my application.

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.