Ponyc: Don't force underscore prefix for private field, method, class etc.

Created on 2 Feb 2016  路  15Comments  路  Source: ponylang/ponyc

In my opinion, underscore prefix unnecessarily combines identification and visibility concerns into one.

I see the following disadvantages of the prefix:

  • Unnecessary entanglement

Pony language elements have a few dimensions: identity, visibility, type and capability. Identity and visibility are combined into one, even though they are not related. It causes a code entanglement: making package private class visible publicly results in lots of changes in classes of the same package.

  • Code noise

Random example from a code base (tcpconnection.pony):

if not _connected and not _readable and (_pending.size() == 0) then
  @asio_event_unsubscribe(_event)
end

I don't think underscores provide useful information. Furthermore, writing the code requires developer to not only remember the names but also the visibility.


I'm aware this would make a big impact on a current codebase, but the more time pass the harder it is to change. I hope it's not too late :)

Most helpful comment

I feel like the implicit name-based privacy thing came back with Go, and I don't feel like this was well-founded - in my opinion, they were favoring brevity over correctness.

As others pointed out, Pony doesn't need this - the compiler makes the guarantees.

As Jonas said, when you bring this up with the Go community, they get defensive.

In most OO communities, prefixing private or protected members with an underscore is actually widely regarded as bad practice - others have point out some of the reasons above. I could add to those arguments, but I'm not going to - the reasons not to do this (in languages where the underscore prefix isn't part of syntax) are well-known and (in my experience) widely accepted.

The questions surrounding this controversy aren't ever fully addressed - when you call out languages like Go for this, the discussion often ends in name-calling, you get accused of trolling, etc.

Why is it you think that tangling up the member visibility constraints with the method-name is a good idea? The answer is often that it makes it visible at the call-site when you're accessing something private. This answer naturally leads to other questions though, like "why is that important?"

Or why, then, is it less important to be able to see which type of member I'm accessing? (the dreaded i_ for integer prefix etc.)

Why is it any less important to be able to identify any other circumstances or meta-properties of any accessed member at any call-site?

It's a controversial choice - controversial because it leads to debates like this, and because not having this feature typically doesn't lead to any discussion at all. No on complains if you have to use a keyword rather than a naming-convention. No one tends to complain if you can't see at call-sites whether the accessed member is private, is an integer or string, etc.

Furthermore, nothing prevents authors who insist on making those things visible in their APIs from using such conventions. (Perhaps other than the communities where this is regarded as bad practice.)

Why should it, for example, be a breaking change in my package, if I decide to change the visibility of a member to make it public? Even if you can automate such changes with an IDE, it does affect refactoring and source-control.

Above all, it's my opinion that making member-names mean something is unnecessary complexity - member names that mean nothing are simpler, because you name them only according to one concern: meaning.

Pony loves to cite correctness. I would claim that visibility and member-names are unrelated issues - making one an effect/artifact of the other, in my opinion, is arguably incorrect.

Since changing this would be a BC break, here's what I'd propose:

  • Add a privacy flag.

  • Retain the _ convention for backwards compatibility, but in addition allow explicit declaration of private members with a keyword.

  • Deprecate and warn about using the _ prefix when it's used to implicitly declare something private, but don't warn about the _ prefix if the member is also flagged as private.

That is, stay away from further opinion-based controversies - let developers continue to use the _ prefix naming-convention internally in their packages, only forcing them to be explicit about what's private.

This will ensure that private members are declared as private, that public members do not start with an underscore, but will provide the freedom to choose whether you want the controversial _ prefix on private members internally in your own packages, so likely won't upset anybody.

All 15 comments

Visibility by keyword makes visibility invisible at the call-site :) In other words, if I see code that says:

x.foo = y
x._bar()

I know immediately that I am assigning to a public field, or calling a private method.

This same approach is used by convention in Python, and enforced by the compiler in Go (albeit using a capital letter for public names, as opposed to an underscore for private names). A quick web search will turn up lots of Python and Go discussions of why this has proven useful in those communities.

In your example, every one of those underscores gives me immediate semantic information. I know that _connected, _readable, _pending, and _event are private fields of this, without knowing the type or implementation details of this. Far from noise, I find that to be critical semantic information when I read code.

In your refactoring example, Pony has an LL(1) grammar. One of the (many) reasons for this is to make it simple to write refactoring tools to do just the sort of thing you mention, i.e. change a name semantically rather than syntactically. Of course, those tools don't exist yet, but using a keyword for visibility instead of a namespace wouldn't make them any easier to write :)

The _ is very unnatural for many in OO languages. I think visibility is more of a constraint. If contents can be modeled in the language perhaps though annotation processing then you do not need this baked into the language. You can have many shades and variation of accessibility.

More natural syntax for the language would be var hunger_level: U64 private or more succinctly as var hunger_level: U64 _ where _ is an alias for private.

I agree with @sylvanc on this one, having the visibility explicitly in the code can be quite useful.

Avoiding the many visibility levels that other languages have was one of the motivations for using a scheme like this. For example, C# has 5 different visibilities. As a result most programmers don't actually know what they all mean and when to use them. In addition, without Java style inheritance there's no need for most of the common visibility levels.

Maybe this can be convention than a requirement.

@sylvanc,

Python does not enforce visibility, thus underscore prefix convention is the way to encapsulate implementation details. However, it's not the case for Pony.

I'm probably bad at searching, because I couldn't find any rational discussion containing benefits of Go's visibility in names (I would appreciate a link to one). It was mainly "I like it" vs "I don't like it", which I don't consider a good argument.

Visibility by keyword makes visibility invisible at the call-site :) In other words, if I see code that says:

x.foo = y
x._bar()

I know immediately that I am assigning to a public field, or calling a private method.

Could you give an example where removing a visibility underscores would make a code ambiguous?

In your example, every one of those underscores gives me immediate semantic information. I know that _connected, _readable, _pending, and _event are private fields of this, without knowing the type or implementation details of this.

It does give semantic information. A type in the name _u8_age would give even more information. I know this one is an absurd example, but it illustrates the question: why a visibility is so special that it's engraved in the name? The more rational example could be a question mark for partial functions: x.call_me?(). Isn't that useful information to have at the call-site?

In my opinion such semantic information should be provided by the IDE/editor rather than forced by a compiler.

In your refactoring example, Pony has an LL(1) grammar. One of the (many) reasons for this is to make it simple to write refactoring tools to do just the sort of thing you mention, i.e. change a name semantically rather than syntactically. Of course, those tools don't exist yet, but using a keyword for visibility instead of a namespace wouldn't make them any easier to write :)

Let me continue on the same example. The visibility was refactored and now it's time to commit changes to version control system: there are numerous lines changed. Consequently, it may result in merge conflicts, makes it harder to do code review and analyse code using blame/annotate.

I'm not trying to guess future problems. It's just my general observation that when a single responsibility principle is violated or things are used in the way other than they were designed for (for example MD5 as a password hash function) it leads to various complications.

If something is private it will be compiler error to access it at any call site. No need extra notation. Visibility should be treated like a capability.

I don't buy the arguments presented here.

  • Unnecessary entanglement

If you have to change the visibility from private to public you have to change all variables, not just the declaration. This is a valid point. But it should be hard to change all instances from private to public as it pollutes your whole class. You have to do the same when changing capabilities in method arguments, declarations and fields.

  • Code noise

_I don't think underscores provide useful information. Furthermore, writing the code requires developer to not only remember the names but also the visibility._

Not? Having to write the visibility as with an upperscore leading char (globals in Ruby) or an underscore (Go, Perl, Python, Pony, ...) is better than having to remember the visibility declaration somewhere else.
Furthermore the underscore is easy to highlight, easier than a private or visibility attribute in the declaration.

_ is very natural in OO languages, that's why so many have adopted this convention.

If the code compile underscore or no underscore, when you call must be visible. Also IDE can help when writing. Underscore is noise when there is too many of them.

Also to be coherent with rest of the language, visibility is a capability and should be treated similarly.

@rurban,

But it should be hard to change all instances from private to public as it pollutes your whole class.

Underscore prefix makes it harder to change visibility in general (as opposed to only private -> public): if you want to cleanup API and make a field/method from public to private it's equally hard.

Here's a real example: I've just filed a pull request to hide String.offset_to_index(). The method initially was public, but was never used outside the String class. Furthermore, I'd like to argue that a need to use this method outside the class manifests a missing method on the String.

This simple adjustment caused modifications in 20 other unrelated places (16 methods).

If there would be a visibility keyword in declaration, then private by default would prevent _polluting your whole class_.

fun private_method() => None // private by default
fun shared shared_method() => None // hypothetical keyword "shared"

I don't think underscores provide useful information. Furthermore, writing the code requires developer to not only remember the names but also the visibility.

Not? Having to write the visibility as with an upperscore leading char (globals in Ruby) or an underscore (Go, Perl, Python, Pony, ...) is better than having to remember the visibility declaration somewhere else.

The whole point of getting rid of underscore prefix is to not have to remember the visibility at all. Compilers are good at this, humans are usually not. Following the String.offset_to_index() example, if any of the _string.pony_ contributors learned visibility of offset_to_index(), my change will be a surprise next time they use it. Although nothing changed conceptually: the contract is still the same.

I would also like to add my 2c: I think visibility and naming _should_ be tied together.

Having a visibility specifier in the declaration makes this information invisible at the call site and I think it is useful to know if I am calling public (proper api) or private (violating the public api)

For example if I am writing a class and in order to do certain frequent operations I need to rely on a private method, that may be a sign that my public api is lacking needed functionality, it is nice to be able to see this at a glance (by looking for _).

This is one of those issues where we're never all going to agree. I'd say that no decisive argument has been made either way and hence it makes sense to keep things how they are for now.

I'm going to close this issue, but feel free to add further comments if there's anything important that you think hasn't been said.

At least I tried :)

I was looking for more information and tried talking with Go users for advantages/disadvantages, but it seems to be a sensitive topic for them. As soon as private/public is mentioned they get in defensive mode and discussion is usually over (I guess too many trolls have already attacked).

I feel like the implicit name-based privacy thing came back with Go, and I don't feel like this was well-founded - in my opinion, they were favoring brevity over correctness.

As others pointed out, Pony doesn't need this - the compiler makes the guarantees.

As Jonas said, when you bring this up with the Go community, they get defensive.

In most OO communities, prefixing private or protected members with an underscore is actually widely regarded as bad practice - others have point out some of the reasons above. I could add to those arguments, but I'm not going to - the reasons not to do this (in languages where the underscore prefix isn't part of syntax) are well-known and (in my experience) widely accepted.

The questions surrounding this controversy aren't ever fully addressed - when you call out languages like Go for this, the discussion often ends in name-calling, you get accused of trolling, etc.

Why is it you think that tangling up the member visibility constraints with the method-name is a good idea? The answer is often that it makes it visible at the call-site when you're accessing something private. This answer naturally leads to other questions though, like "why is that important?"

Or why, then, is it less important to be able to see which type of member I'm accessing? (the dreaded i_ for integer prefix etc.)

Why is it any less important to be able to identify any other circumstances or meta-properties of any accessed member at any call-site?

It's a controversial choice - controversial because it leads to debates like this, and because not having this feature typically doesn't lead to any discussion at all. No on complains if you have to use a keyword rather than a naming-convention. No one tends to complain if you can't see at call-sites whether the accessed member is private, is an integer or string, etc.

Furthermore, nothing prevents authors who insist on making those things visible in their APIs from using such conventions. (Perhaps other than the communities where this is regarded as bad practice.)

Why should it, for example, be a breaking change in my package, if I decide to change the visibility of a member to make it public? Even if you can automate such changes with an IDE, it does affect refactoring and source-control.

Above all, it's my opinion that making member-names mean something is unnecessary complexity - member names that mean nothing are simpler, because you name them only according to one concern: meaning.

Pony loves to cite correctness. I would claim that visibility and member-names are unrelated issues - making one an effect/artifact of the other, in my opinion, is arguably incorrect.

Since changing this would be a BC break, here's what I'd propose:

  • Add a privacy flag.

  • Retain the _ convention for backwards compatibility, but in addition allow explicit declaration of private members with a keyword.

  • Deprecate and warn about using the _ prefix when it's used to implicitly declare something private, but don't warn about the _ prefix if the member is also flagged as private.

That is, stay away from further opinion-based controversies - let developers continue to use the _ prefix naming-convention internally in their packages, only forcing them to be explicit about what's private.

This will ensure that private members are declared as private, that public members do not start with an underscore, but will provide the freedom to choose whether you want the controversial _ prefix on private members internally in your own packages, so likely won't upset anybody.

We have a RFC process for such changes. If you are interested, please feel free to open a RFC.

https://github.com/ponylang/rfcs/blob/master/README.md

Was this page helpful?
0 / 5 - 0 ratings

Related issues

willemneal picture willemneal  路  5Comments

Theodus picture Theodus  路  6Comments

jemc picture jemc  路  9Comments

jemc picture jemc  路  5Comments

russel picture russel  路  7Comments