Syntactical sugar for `getfield`

Each time I implement a new getproperty method for a structure I found myself filling the code with lengthy (and sometimes nested) calls to getfield. So far the best looking solution I came up with is:

⋄ = getfield    # type \diamond<TAB>

It also has a relatively high precedence with respect to other operators.

MWE:

import Base.getproperty
⋄ = getfield

struct MyStruct
    greet::String
end

function getproperty(s::MyStruct, name::Symbol)
    @info "Accessing the $name property"
    s ⋄ name # in place of the longer `getfield(s, name)`
end

s = MyStruct("Hi")
s.greet * " there!"
s⋄:greet * " there!"   # correct operator precedence

Is there any other recommended, or widely used syntactic sugar for getfield ?

2 Likes

For an immutable struct, I’ve used this pattern:

struct A
	x::Int
	d::Dict{Int,Int}
end

x(a::A) = getfield(a, :x)
d(a::A) = getfield(a, :d)
julia> a = A(42, Dict(1 => 2, 3 => 4))
A(42, Dict(3 => 4,1 => 2))

julia> x(a)
42

julia> d(a)[5] = 6
6

julia> d(a)
Dict{Int64,Int64} with 3 entries:
  3 => 4
  5 => 6
  1 => 2

Note that with this pattern it is still pretty easy to mutate the contents of the dictionary.

Is there a reason not to just overload getproperty and then use the normal . syntax?

1 Like

@marius311 I’m not quite sure what you mean by that. Can you give an example?

that would be preferred as

The syntax a.b calls getproperty(a, :b).

@CameronBieganek:

julia> struct A
           x::Int
           d::Dict{Int,Int}
       end

julia> a = A(5, Dict([1=>2, 2=>3]))
A(5, Dict(2 => 3, 1 => 2))

julia> a.x
5

julia> a.d[2]
3

Clearly in my brief example I left out the part where I overload getproperty

Its still nice to define these methods if you don’t want the user to use the fields directly. DataFrames defines getproperty to work with columns but then also has internal methods for accessing the actual fields of a data frame.

1 Like

I agree. Often, I use e.g.

struct Struct
  value::Int
end

value(x::Struct) = x.value

An additional benefit is that this may simplify a later modification.

Here’s a better example than my previous one:

struct PropertyDict{V}
	d::Dict{Symbol,V}
end

d(pd::PropertyDict) = getfield(pd, :d)
Base.getproperty(pd::PropertyDict, sym::Symbol) = d(pd)[sym]
Base.setproperty!(pd::PropertyDict, sym::Symbol, val) = ( d(pd)[sym] = val )
julia> p = PropertyDict(Dict(:a => 1))
PropertyDict{Int64}(Dict(:a => 1))

julia> p.a
1

julia> p.b = 2
2

julia> p.b
2

I’m not certain, but my initial thought was that @marius311 meant that you can do something like this:

function getproperty(pd::PropertyDict, sym::Symbol)
    if sym == :d
        getfield(pd, :d)
    else
        getfield(pd, :d)[sym]
    end
end

That pattern can work in some scenarios, but in the case of the PropertyDict above, you want to be able to put any symbol into the dictionary as a key. In other words, you want to allow for the possibility that :d is one of the keys in the dictionary.

That was just a question for the OP, basically why create syntactic sugar for getfield when getproperty already has it.

It seems like we’re talking past each other here. Take a closer look at my second example. The issue arises when you overload getproperty. Once you do that, you can no longer use dot syntax to access the underlying fields of your struct, so you have to run around calling getfield(a, :x) all the time, which gets messy real fast.

1 Like

No, there is not (at least not at the time this is written).

I see that this thread is a bit old (but popped up on the front page now).

But is it really a good idea to let properties and fields share the same names? I see properties as part of the interface, but fieldnames as implementation details, and would think that their names should be decoupled.

And even if you don’t want properties as part of the interface, it seems to me that they should be ‘virtual fields’ that have unique names, separate from the fieldnames.

Yes. This is not problematic, but convenient, as fields provide a default for properties.

Of course, one is free to design an API where the two are distinct.

I’m afraid my initial question was slightly misleading. I’ll try to rephrase as follows:

The default implementation of getproperty is to call getfield, and the dot-syntax is a sugar for getproperty. Hence, in my module implementation, I can access structure fields with struct.fieldname.

Now assume I want to provide a high-level interface(*) by implementing a getproperty method for my structure definitions. All my struct.fieldname statements would no longer works, and I need to explicitly invoke getfield (possibily several times in the same statement, when dealing with nested structures…).

Is there any alternative, commonly adopted, sugar for getfield?

According to above answers, I guess the final answer is “no”.
Which is not necessarily a big deal: works for me. It is just aesthetically unpleasant…


(*): e.g. to protect the implementation details and, most importantly, provide access to views over specific fields of multi-level nested structures.

But the key point here is to assume there is a very good reason to overload getproperty (to simplify usage outside my module), while still accessing fields with getfield (within my module).
Answers like “Why do you need that?”, “Do this instead”, etc. are attempts to circumvent this assumption…

1 Like

I don’t know any conventions, but you can do something like this for internal use… can’t avoid typing the : without a macro though:

julia> (..)(x, s::Symbol) = getfield(x, s);

julia> (1:3)'..:parent
1:3
1 Like

Yes this is nicer, but operator precedence is not correct.

The following raises an error:

(..)(x, s::Symbol) = getfield(x, s);
(1:3)'..:parent .+ 1

while

⋄ = getfield
(1:3)'⋄:parent .+ 1

works as expected.

Oh yea, good point. You could shadow ^ since that has high precedence, and is easy to type:

^(x, s::Symbol) = getfield(x, s)
^(x,y) = Base.:^(x,y) # fall-through on other types
(1:3)'^:parent .+ 1

There was an issue somewhere to make some more double-ascii operators with varying precedence, there is ++ but say ^^ would be neat here.

4 Likes

So far, ^ is the best solution!!
Thanks.