Assigning to a getter function

Getter and setter functions

Recently I stumbled on some interesting behaviour when using a “getter”-like function in combination with broadcasting calls. For the sake of argument lets assume:

abstract type AbstractPoint{T} end

mutable struct Point{T} <: AbstractPoint{T}
    position::Vector{T}
end

where the position method returns the position field, assuming that the interface for AbstractPoint requires that every inheriting type has such a field:

position(p::AbstractPoint) = p.position

As far as I have observed it, a Julia-esque way to update the position of an instance of Point and other AbstractPoints would be:

position!(p::AbstractPoint, new_pos) = (p.position = new_pos)

Assigning to a getter function?

However, what also appears to work, is to write:

function position!(p::AbstractPoint, new_pos)
    position(p) .= new_pos
    return nothing
end

The above function only works when broadcasting .= and, naturally, also for non-mutable versions of Point. I am not certain why this works, but it enables the reuse of position! by other subtypes of AbstractPoint when changing the getter method for position(...), i.e., if those subtypes do not have a field position but rather a composite type that does have a position field.

This syntax does not work with non-Array types.

The question

Has anyone here ever used the above syntax for a setter function intentionally? In which ways can this cause unwanted effects? I’m still to new to Julia to be familiar enough with the broadcasting system to fully understand the workings of this code. The use cases for this are rather limited, since it only works for Arrays, but my benchmarking attempt has shown no allocations for either version of position!. Feedback is highly welcome :slight_smile:

1 Like

You are not really assigning to a “getter” function. You are assigning to a vector returned by a getter function. Your definition of position! is equivalent to the one below and it is perfectly valid.

function position!(p::AbstractPoint, new_pos)
    _position = position(p) # get the vector
    _position .= new_pos # set the elements of the vector via broadcast
    return nothing
end

As a side note, you may find this immutable version is more performant than the mutable version.

julia> struct StaticPoint{T,N} <: AbstractPoint{T}
           position::NTuple{N,T}
       end

julia> StaticPoint((3,5))
StaticPoint{Int64, 2}((3, 5))
1 Like

Thank you for the response. Regarding StaticPoint, would I still be able to modify the position field? This only works in the above example due to the broadcasting call.

In general, no. The idea is that if you need to modify the position field, then it might be better just to create a new StaticPoint.

As for why this might be better, see the discussion here:
https://stackoverflow.com/questions/59737850/why-are-immutable-structs-default-in-julia