Creating an Alias for a Struct Field

I am trying to create a Particle Struct.
I was wondering if there is a way to have particle.pos reference particle.position as an alias

Is there any way to have such a behaviour? I might set pos as another field but then how would it change if say I mutate position

mutable struct Particle
    position
    #pos 
end

Is it something computationally light? (Is observables light?)

Using the dot syntax to access fields of a struct are converted into calls to getproperty and setproperty! so you could just define these.

getproperty(p::Particle, sym) = getfield(p, sym === :pos ? :position : sym)
setproperty!(p::Particle, sym, x) = setfield!(p, sym === :pos ? :position : sym, x)
2 Likes

position(p :: Particle) = p.pos would be idiomatic.

3 Likes

I think you have to define getproperty(p::Particle, sym::Symbol) as otherwise you’ll get an ambiguity error.

1 Like

To get tab-completion you should also define propertynames.

2 Likes

I’ll echo an above post advocating that, while modifying getproperty is possible, an accessor function like pos(p::Particle) or position(p::Particle) is often nicer (in my experience).

If .position is too long, you could always just name the field pos to begin with.


As a rather significant tangent, and without knowing what it is you’re doing, I find that a struct (rather than mutable struct) is often much more convenient (and sometimes more performant) for these (and most) sorts of objects. Rather than mutate a Particle, it’s often just as fast to generate an entirely new one with most of the fields copied from the previous. The compiler is pretty good at this and might choose to mutate the old one anyway, if the old one is not needed any more (and if the old one still is needed, then mutating it to produce the new one is going to be a problem).

I’ll also recommend you use SVector (probably SVector{3,Float64}) from StaticArrays.jl for the position field. Note that this is also immutable, which slightly changes how one interacts with it compared to Vector{Float64}.

I was thinking it had to be Mutable. But as it turns out, Observables allow you to mutate immutable objects (if that makes sense)

I’m doing something like:

using Observables, GeometryBasics

struct Particle2D{T<:Real}
    position::Observable{Point{2, T}}
    velocity::Observable{Point{2, T}}
    acceleration::Observable{Point{2, T}}
    mass::T
    charge::T
    shape::Observable
    size::Observable{T}
    alpha::Observable{T}

    function Particle2D(
        position::Point{2, T}=Point2i(0,0),
        velocity::Point{2, T}=Point2i(0,0),
        acceleration::Point{2, T}=Point2i(0,0),
        mass::T=1,
        charge::T=1,
        shape=GeometryBasics.Circle,
        size::T=1,
        alpha::T=1
    ) where {T<:Real}
        new{T}(Observable(position), Observable(velocity), Observable(acceleration), mass, charge, Observable(shape), Observable(size), Observable(alpha))
    end

    function Particle2D(
        position::Point{2, <:Real}=Point2i(0,0),
        velocity::Point{2, <:Real}=Point2i(0,0),
        acceleration::Point{2, <:Real}=Point2i(0,0),
        mass::Real=1,
        charge::Real=1,
        shape=GeometryBasics.Circle,
        size::Real=10,
        alpha::Real=1
    )
        super_T = promote_type(
            eltype(position),
            eltype(velocity),
            eltype(acceleration),
            typeof(mass),
            typeof(charge),
            typeof(size),
            typeof(alpha)
        )

        new{super_T}(
            Observable(convert(Point{2, super_T}, position)),
            Observable(convert(Point{2, super_T}, velocity)),
            Observable(convert(Point{2, super_T}, acceleration)),
            convert(super_T, mass),
            convert(super_T, charge),
            Observable(shape),
            Observable(convert(super_T, size)),
            Observable(convert(super_T, alpha))
        )
    end
end



"""
julia> Particle2D()
Particle2D{Int64}(Observable([0, 0]), Observable([0, 0]), Observable([0, 0]), 1, 1, Observable(Circle), Observable(10), Observable(1))

julia> Particle2D(Point(1,2))
Particle2D{Int64}(Observable([1, 2]), Observable([0, 0]), Observable([0, 0]), 1, 1, Observable(Circle), Observable(10), Observable(1))

julia> @benchmark Particle2D()
BenchmarkTools.Trial: 10000 samples with 168 evaluations.
 Range (min … max):  644.048 ns … 23.988 ΞΌs  β”Š GC (min … max): 0.00% … 95.21%
 Time  (median):     676.786 ns              β”Š GC (median):    0.00%
 Time  (mean Β± Οƒ):   768.488 ns Β±  1.027 ΞΌs  β”Š GC (mean Β± Οƒ):  9.29% Β±  6.68%

  β–„β–‡β–ˆβ–†β–‡β–‡β–…β–ƒβ–‚β–β–‚β–ƒβ–‚β–‚β–‚β–‚β–‚β–ƒβ–‚β–‚β–‚β–β–β–β–‚β–β–β–β–                                β–‚
  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‡β–†β–†β–…β–†β–†β–†β–„β–†β–…β–…β–†β–…β–„β–…β–β–…β–ƒβ–„β–ƒβ–ƒβ–β–ƒβ–β–…β–…β–„ β–ˆ
  644 ns        Histogram: log(frequency) by time       1.1 ΞΌs <

 Memory estimate: 1.36 KiB, allocs estimate: 27.

 
---


julia> @benchmark Particle2D().position[] += [1,2]
BenchmarkTools.Trial: 10000 samples with 98 evaluations.
 Range (min … max):  793.878 ns … 61.263 ΞΌs  β”Š GC (min … max):  0.00% … 97.09%
 Time  (median):     905.102 ns              β”Š GC (median):     0.00%
 Time  (mean Β± Οƒ):     1.095 ΞΌs Β±  2.381 ΞΌs  β”Š GC (mean Β± Οƒ):  12.22% Β±  5.54%

  β–†β–ˆβ–†β–†β–„β–‡β–†β–†β–†β–…β–„β–„β–„β–„β–ƒβ–ƒβ–‚β–β–                           ▁ ▁ ▁          β–‚
  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‡β–ˆβ–†β–†β–…β–†β–†β–…β–…β–„β–…β–…β–†β–…β–…β–…β–†β–†β–†β–†β–†β–‡β–‡β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‡β–‡β–‡β–‡β–…β–†β–† β–ˆ
  794 ns        Histogram: log(frequency) by time      1.92 ΞΌs <

 Memory estimate: 1.56 KiB, allocs estimate: 32.

"""

as for position, velocity, acceleration… i guess I should name them pos, vel, acc to begin with.

I can not redefine since I do need Observables and need to redefine it, but it happens with immutable structs too somehow! Thanks for that!

Pardon me if I misunderstood you, I am still learning.
I didn’t catch that.

julia> mutable struct Particle
       position
       pos
       end

julia> Particle(x) = Particle(x, x)
Particle

julia> getproperty(p::Particle, sym) = getfield(p, sym === :pos ? :position : sym)
getproperty (generic function with 1 method)

julia> setproperty!(p::Particle, sym, x) = setfield!(p, sym === :pos ? :position : sym, x)
setproperty! (generic function with 1 method)

julia> p = Particle(1)
Particle(1, 1)

julia> p.position = 2
2

julia> p
Particle(2, 1)

julia> setproperty!(p, :position, 3)
3

julia> p
Particle(3, 1)

The intent was to have one field in Particle that can be accessed in two ways:

julia> mutable struct Particle
              position
              end

julia> getproperty(p::Particle, sym :: Symbol) = getfield(p, sym === :pos ? :position : sym)
getproperty (generic function with 1 method)

julia> setproperty!(p::Particle, sym :: Symbol, x) = setfield!(p, sym === :pos ? :position : sym, x)
setproperty! (generic function with 1 method)

julia> p = Particle(1)
Particle(1)

julia> p.position = 2
2

julia> p
Particle(2)

julia> setproperty!(p, :pos, 3)
3

julia> p
Particle(3)