Eugene Barsky
Eugene Barsky

Reputation: 6012

Using public and private methods inside their class

If I have a public method, I can call it inside its class using both $.name and self.name:

class TEST {
  has Int $.a;

  method b($x) {
    return $!a * $x;
  }

  method c($y) {
    return self.b($y) * 3; # or $.b($y)
  }
}

my $m = TEST.new(a => 10);
say $m.c(2); # 60

But if I make b a private method, I only can call it with self!b, not $!b, otherwise I get the following error message:

Attribute $!b not declared in class TEST

What's behind this rule? What are the rules of calling a method inside its own class?

Upvotes: 12

Views: 331

Answers (2)

uzluisf
uzluisf

Reputation: 3086

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.

Upvotes: 1

Elizabeth Mattijsen
Elizabeth Mattijsen

Reputation: 26969

An attribute can always be referred to as $!foo in a class. If you do that, than the code will be generated to directly access the attribute itself, and any classes subclassing your class will not be able to change this behaviour.

If you use has $.foo in the declaration of a class, it means that a public accessor will be created for you (and if you add is rw it can also function as a mutator).

When you use $.foo in your code otherwise, it is exactly the same as $( self.foo ). This means that it will call the method foo on self, and itemize the return value (make it a single "thing" if it wasn't yet). This will go wrong if you defined your attribute with $!foo and you did not supply a method foo yourself.

This goes even further: $.bar really means self.bar: you only need to have a method existing by the name bar, which may not be related to any attribute at all.

If you define a private method !baz, the ! just indicates the privacy of the method, which means you need to call it indeed as self!baz. There is no short syntax for it.

Personally I dislike the fact that you can say $.zippo even if zippo is not an attribute. But I'm afraid that ship has sailed. But this behaviour is now causing you confusion :-(

So what's behind the rule for not having a short syntax for calling a private method? Not sure, I guess really that $!foo was already taken to mean direct access to the attribute, and provide you with a compile time error if the attribute doesn't exist.

Hope this answers your question!

Upvotes: 11

Related Questions