NOTE: This is mostly a rehash of lizmat's answer but I figured the way I've come to reason about this issue might add some value.
From experienced Rakoons, I'd like to know if this way of wording the discourse about private vs public attributes in Raku makes sense, whether it's correct or simply misrepresents the issue.
TLDR
- In Raku, there are only private attributes which are declared and accessed using
$!
.
- So-called public attributes are simply private attributes with accessor methods, whose accessibility can be altered using traits.
- All public methods can be called using contextualized method invocation ❶ inside their classes, which has the effect of contextualizing their return values.
- There's no contextualized method invocation for private methods because if there was, it would conflict with the private attribute syntax. Hence, why you can do
$.method
but not $!method
.
- To avoid confusion, simply avoid contextualized method invocation and instead contextualize the method's return value yourself.
Preface
I'll start by saying that there are only private attributes in Raku, which you declare with $!
. This statement obviously depends on how you define private vs public attributes, and I even make the case by the end of the section on why the Raku community makes the distinction between them. In the context of this answer, a true public attribute is one that's truly accessible to the outside world upon initialization. For example JavaScript has true public attributes:
class Foo {
constructor() {
this.value = 42;
}
}
const bar = new Foo();
console.log(bar.value); #=> 42
bar.value = 56;
console.log(bar["value"]); #=> 56
The only thing that prevents the programmer from messing an object's state directly is through convention. Either the consumer of the class knows beforehand that a class's attributes shouldn't be accessed directly or there's some way of signaling which attributes shouldn't be accessed at all. For example, in both JavaScript and Python, the author of a class signals to other programmers an attribute is "private" by prefixing it with an underscore. Thus, in this sense, those languages only have public attributes which are protected from the outside through convention; Raku approaches it from the opposite side: it has only private attributes which are made accessible to the outside world through language constructs.
Now assuming you agree with the statement "there are only private attributes in Raku", let's continue. If there are only private attributes, we can simply drop the "private" qualifier since it's redundant. Unlike in other programming languages, you cannot access an attribute outside its class unless you go out of your way to facilitate it using methods either explicitly or implicitly. By creating methods explicitly, you can use access it, mutate it, etc., and doing these things is so common that Raku provides sensible defaults to access, mutate, and restrict attributes using constructs such as traits.
The following table is an attempt at summarizing these defaults (I've prefixed each row in the first column with "(private)" as a reminder you're always dealing with private attributes in Raku classes):
You want a |
You declare it with (assume has ) |
Comment |
(private) attribute |
$!attr |
Attribute isn't accessible to the outside world |
(private) attribute that can be set in the default constructor |
$!attr is built |
Attribute can be set in the default constructor, otherwise it's not accessible to the outside world |
(private) attribute with an accessor method |
$.attr |
Attribute is completely accessible to the outside world, including setting it in the default constructor |
(private) attribute with an accessor method but cannot be set in the default constructor |
$.attr is built(False) |
Attribute is accessible as read-only, and thus cannot be set in the default constructor |
(private) attribute with a mutator method |
$.attr is rw |
Attribute is completely accessible to the outside world, including mutating it after initialization as obj.attr = value |
(private) attribute with an accessor method and not optional at object initialization |
$.attr is required |
Attribute is completely accessible to the outside world, and must be set at object initialization |
(private) attribute that can be set in the default constructor and not optional at object initialization |
$!attr is built is required |
Attribute must be set in default constructor, otherwise it's not accessible to the outside world |
Now "(private) attribute with an accessor/mutator method" is quite the mouthful, which is why the Raku community calls attributes declared with $.
(or any other sigil) public attributes, with the proviso the reader understands they're indeed private attributes with a few behaviors tacked onto them as dictated by the twigil (.
) and the applied trait. This is the case I alluded to at beginning: despite not being public per se by the above definition, it makes sense to call them public since they exhibit many of the things a true public attribute is, especially in contrast to private attributes. It also makes talking about them more succint and easier, e.g., "(private) attribute with an accessor method but cannot be set in the default constructor" simply becomes "public attribute that cannot be set in the default constructor".
I think that knowing now $.attr
is an attribute with an accessor method makes it easier to understand why you can access the attribute either with $.attr
or self.attr
: you're simply accessing the attribute through its accessor method. However, as explained below, a method such as $.method
isn't necessarily tied to an attribute per se: all public methods can be invoked using that syntax.
Method invocation using self
and sigils
In general, contextualized method invocation, e.g., $.method
, simply means you're applying the sigil's context to the method's return value. Thus, for example, $.method
is a shorthand for $(self.method)
or self.method.item
. However since $.
is also used to declare public attributes, using it with a method might mislead the reader into thinking there's an attribute with that same name when there might not be one. Similarly, it might mislead the beginner Rakoon into thinking private method invocation using sigils, e.g., $!method
, is possible when in fact doing so simply throws a compilation error. Because contextualized method invocation can be misleading, I suggest a bit of prescriptivism ❷:
Avoid using $.
, @.
, or %.
inside a class unless to declare a public attribute:
- If you want to access a public attribute directly, use
$!
, @!
, or %!
, e.g., $!attr
.
- If you want to access a public attribute indirectly, call its accessor method on
self
, i.e., self.attr
.
- If you want to apply context to a public method's return value, contextualize it yourself, e.g.,
self.attr.item
or $(self.attr)
.
❶ I just came up with this but it's more compact than "method invocation using sigils" or listing out the sigils.
❷ This doesn't make much sense in the Land of TIMTOWTDI but try we must.