Regarding `getproperty` and public vs. private APIs

The syntax x.f calls getproperty which is very widely used. Don’t confuse it with getfield which is the default fallback of getproperty.

2 Likes

If I understand right, some people feel that struct properties should not be considered public API.

That was certainly the case before getproperty could be overloaded. Now the option to change the behavior is there and it’s less of a problem to expose properties to the user. Many packages, including standard libraries like LinearAlgebra, make use of this in exposed api.

5 Likes

Why do they feel like this?

This is probably influenced by Java, where the usual pattern is to define class properties as private and to write boilerplate getters and setters to access them.
Even in Python (as “OOP” language) it is considered to be perfectly fine to have class properties as public API. You can use property methods to change internal behavior without changing the API if required (you can do the same in Julia by adding new getproperty / setproperty! methods).

3 Likes

Because a large number of posts on this forum have claimed (some of them mine) that fields are internal implementation details that should not be accessed except from internal functions.

This guideline seems to have weakened somewhat with the introduction of of getproperty, and now the official position is quite unclear to me, but personally, I still consider fields internal and ‘off-limits’.

5 Likes

I wish there would be a good way to indicate what properties / functions are public api without the rigidness of Java’s private / public. In Python an underscore is used as convention to indicate internals, but this is not widely adopted in Julia.

4 Likes

But in python properties/attributes are public by default, and you mark them specially to indicate that they are private. If you don’t want ‘public by default’, what would be a good convention for explicitly marking them as public?

Good question - I agree that explicitly marking for public would be better than marking for private.

For widely used packages, it is indeed often helpful to provide interfaces to separate from internals. But I also find it helpful to directly access person.name in everyday (i.e. most) coding. Not everybody wants to deal with getters and setters all the time.

what would be a good convention for explicitly marking them as public?

How about documentation? For example, ? lu documents property L (lower triangular), and people merrily use it to without needing details, and the actual fields are thus “safe.” What would be the point of making fields private in the Java sense? In the open source era, private only makes it slightly more annoying to access if someone really wants to. (Actually for closed-source too.)

Encapsulation in OOP never panned out as initially advertised. Yes, you could hide information/implementation, but that also hindered composabiity.

@lungben explicitly marking for public would be better than marking for private

Not for me. I like how fields/methods are publicly accessible Julia. It is reflective of the trend called open source. It’s more realistic and practical to document how people should access a struct, and not pretend there are ways they can’t, which is just security theater.

2 Likes
struct Person
  @public name
  age
end

or

struct Person
  export name
  age
end

or

struct Person
  name
  age

  export name
end

Method propertynames accepts an optional second argument, which is a boolean to indicate whether private properties should be returned.

help?> propertynames
search: propertynames

  propertynames(x, private=false)

  Get a tuple or a vector of the properties (x.property) of an object
  x. This is typically the same as fieldnames(typeof(x)), but types
  that overload getproperty should generally overload propertynames as
  well to get the properties of an instance of the type.

  propertynames(x) may return only "public" property names that are
  part of the documented interface of x. If you want it to also return
  "private" fieldnames intended for internal use, pass true for the
  optional second argument. REPL tab completion on x. shows only the
  private=false properties.
3 Likes

This has gradually meandered away from object.method(args...) so I split it into its own focused thread. It wasn’t a completely clean split, but I believe I managed to preserve the relevant context in both places.

Carry on!

6 Likes

Not talking about making fields inaccessible, but about some convention for indicating whether they are internal implementation details, or part of the API. Putting an underscore in front is no good if you want ‘private by default’.

I would never do that, but use (L, U) = ... instead. I’m sad to see this convention weakening. Getters and setters aren’t something you ‘have to deal with’, they are just better. How do you broadcast dot access?

I was thinking more along the lines of a naming convention.

This would influence tab-completion, then? That’s a possibility.

But I guess I’m more lamenting what I perceive as the weakening of the convention to avoid field access. That will influence what ‘normal language use’ looks like, and what packages do and provide.

getproperty.(x, :smell)

3 Likes

:cry:

I wish there would be a good way to indicate what properties / functions are public api without the rigidness of Java’s private / public. In Python an underscore is used as convention to indicate internals, but this is not widely adopted in Julia.

100% agree. Underscore is such an awesome lightweight way to indicate privacy. In my opinion, more elaborate ways to make stuff private only hurt debugging, testing, exploration etc.

2 Likes

The problem with underscore is that it defaults to public. Indicating public (defaulting to private) is what is needed. I gave some examples of syntax for that above.

2 Likes

Could you elaborate on why you don’t like it?

From my perspective, it does seem silly to have two ways to do a projection. Maybe the reason people want attribute access is just so they don’t have to bother writing

name(p::Person) = p.name

I wouldn’t mind

struct Person
  name
  age
end

@accessors Person name age

p = Person("Alice", 35)
@assert name(p) == "Alice"