Why isn't getproperty defined for Dictionaries?

It would be convenient to have the ability to retrieve Dict values with dot syntax. Seems like methods could be added to getproperty for Dict{String, <:Any} and Dict{Symbol, <:Any} so that values could be accessed the same way you would a DataFrame column:

Dict("key" => "value")."key" == "value"
Dict(:key => "value").key == "value"
1 Like

Personally, I think it should be, but there were some objections during the 1.0 release process. Now it might be considered breaking to change the existing meaning of d.keys and d.values, etc. Of course it can also be argued that fields of types are conventionally private, but that could break quite a lot of code out there.

Not all keys can be properties. For example Dict(1=>2) is fine, but x.1 is not.

1 Like

But it could at least work when keys are symbols, so d.foo would mean d[:foo] which would be extremely useful in many contexts.

And x.:1 is totally fine: it calls getproperty(x, 1).

Ehh, programming styles that only work for a special cases (or require some ā€œnon-standard tricksā€ like @aplavin showed) are generally something Iā€™d avoid. Thereā€™s other issues here too though. x.foo has literal information, and so if you know all of the fields already and have that information at compile-time, why not use a NamedTuple? It would be safer, because x.foo2 = y would create a new field in the Dictionary while it would error with the NamedTuple, so if you know all of the fields at compile-time well enough to write x.foo then you probably want error checking. If you donā€™t know the field at compile-time, then youā€™d do getproperty(x,:foo), in which case you might as well do x[:foo].

So I just donā€™t see where the utility is: either itā€™s dynamic so use a Dictionary and because itā€™s dynamic names you need to x[sym], or itā€™s static so you can x.foo in which case you can construct it like (;foo = y).

7 Likes

There are cases where you donā€™t want to use NamedTuple because you need mutability and also because you have many of these objects and want a unique type to not trigger needlessly many compilation of methods. One example where this dot syntax is convenient if for ā€œconfigurationā€, which can be stored in files for example, which you could load with YAML and manipulate with such a configuration dict; in some contexts you want to assume that some keys do exist (and be able to write statically e.g. config.params), you might have for example validated the data upstream, and at the same time allow arbitrary keys at whatevel level of the configuration.

2 Likes

You run into issues shadowing actual properties of Dicts, no? Dict has properties

julia> propertynames(Dict())
(:slots, :keys, :vals, :ndel, :count, :age, :idxfloor, :maxprobe)

Of course, none of these are part of the public interface so it would technically be possible to bury them under key names (after rewriting all the Dict internals to use getfield rather than getproperty). But having mydict.count mean different things depending on whether :count is a key seems like a recipe for chaos.

Is mydict[:count] really so onerous? NamedTuple also supports this syntax, so itā€™s already a consistent interface for both.

3 Likes

In some cases yes, when you have nested stuff, like d[:a][:b] instead of just d.a.b, in particular when playing with this at the REPL (well, the REPL-completion is kinda broken for this case, I have a patch for this waiting to be PRā€™edā€¦)
(EDIT: Iā€™m not specifically arguing in favor making Dict handle the nice dot syntaxt, just that dot syntax is actually very convenient; I currently just use a custom dict).

Allowing the . for Dict to work like it does for NamedTuple and DataFrame would be more consistent ā€¦
The inconsistency while I was coding today is what brought this to my attention.

# Package Table
df = DataFrame()
df."Quantity" = ["Radial Stress", "Tangential Stress", "Longitudinal Stress", "Equivalent Stress"]
df."Average" = Ļƒ_mean
df."Maximum" = Ļƒ_max
df."Minimum" = Ļƒ_min
df."Max Location" = r[Ļƒ_max_index]
df."Min Location" = r[Ļƒ_min_index]

# Package Unit Dictionary for Table Columns
df_units = Dict{String, Any}()
df_units["Quantity"] = ""
df_units["Average"] = Ļƒ_units
df_units["Maximum"] = Ļƒ_units
df_units["Minimum"] = Ļƒ_units
df_units["Max Location"] = r_units
df_units["Min Location"] = r_units

But I wasnā€™t aware that Dictionaries had inherent properties, so that is probably a good enough reason not to implement this.

GitHub - JuliaCollections/PropertyDicts.jl does this but is out of date. I made a PR a while ago to resolve this but not many people have permissions to repos in JuliaCollections

2 Likes

Frames White (@oxinabox) was kind enough to give me permissions, so PropertyDicts.jl has been updated with a new version.

3 Likes