7stud has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks,

I am trying to understand the following code, in particular the set method::

use v6; class Point { has $.x = 0; has $.y = 0; method gist { "[$.x, $.y]" } method set( :$x = $.x, :$y = $.y ) { $!x = $x; $!y = $y; } }

Can someone explain all of the syntax used in the set method?

And, another question if I may: is "Learning Perl 6" outdated?

Thanks.

Replies are listed 'Best First'.
Re: Raku classes: syntax question
by Athanasius (Archbishop) on Jan 31, 2024 at 08:54 UTC

    Hello 7stud,

    I can’t comment on Learning Perl 6 as I haven’t read it, but here is my understanding of the example Point class. Consider the following code which creates a Point object and sets the y attribute:

    my Point $p = Point.new; $p.gist.put; $p.set( y => 4 ); $p.gist.put; $p.y.put;

    Output:

    17:44 >raku 2091_SoPW.raku [0, 0] [0, 4] 4 18:00 >

    First, the attributes $.x and $.y are declared with dots rather than exclamation points, meaning that although they are private attributes, Raku will generate read-only getter methods for them. So, in the example above, the method call $p.y returns the value of the attribute $.y.

    To set the attributes, it is necessary to provide the class with an explicit set method.

    method set( :$x = $.x, :$y = $.y )
    1. :$x and :$y declare named parameters x and y, allowing the set method to be called like this: $p.set( x => 3, y => 7 );.
    2. = $.x (or the equivalent = $!x) sets the default value for that parameter. So in the call $p.set( y => 4 ); the x attribute, not being named in the method call, receives its default value, namely $.x, ensuring that the assignment $!x = $x; leaves the x attribute unchanged.

    Note: Raku allows you to sometimes use $.x as an alias for $!x, but I find that makes things unnecessarily confusing. I think of it this way: the attribute is $!x, but it is declared as $.x to get Raku to provide it with a read-only getter method. So I would prefer to declare the set method as method set( :$x = $!x, :$y = $!y ). But, of course, YMMV.

    $!x = $x; $!y = $y;

    These statements simply set the object attributes to the values specified as arguments to the set method (or to their default values, if no arguments are specified in the call).

    Hope that helps,

    Athanasius <°(((><contra mundum סתם עוד האקר של פרל,

      :$x and :$y declare named parameters

      I looked up "named parameters", and it turns out the syntax used for the parameters in the set method:

      method set( :$x = $.x, :$y = $.y )

      is the same syntax used for any function:

      Named parameters In contrast to positional parameters, named parameters are referred by their names. The following function takes two parameters called $from and $to: sub distance(:$from, :$to) { $from - $to } Now, to call the function, you need to name the arguments: say distance(from => 30, to => 10); # 20 It is an error to pass the arguments as if they were positional. For example, a call distance(30, 10) generates an error: ...

      I found some basic information on the syntax used in classes here:

      class Rectangle { has Int $.length = 1; has Int $.width = 1; method area(--> Int) { return $!length * $!width; } } my $r1 = Rectangle.new(length => 2, width => 3); say $r1.area();

      We define a new Rectangle class using the class keyword. It has two attributes, $!length and $!width introduced with the has keyword. Both default to 1. Read only accessor methods are automatically generated. It is rarely necessary to explicitly write a constructor. An automatically inherited default constructor called new will automatically initialize attributes from named parameters passed to the constructor.

      'Attributes" (called fields or instance storage in other languages) are never directly accessible from outside of the class (this is in contrast to many other languages). This encapsulation is one of the key principles of object oriented design. The ! twigil (Ed. twigil = secondary sigil) emphasizes that this attribute is private to the class. The attribute is encapsulated. Private attributes will not be set by the default constructor by default...

      And this:

      has Bool $.done;

      This scalar attribute (with the $ sigil) has a type of Bool. Instead of the ! twigil, the . twigil is used. While Raku does enforce encapsulation on attributes, it also saves you from writing accessor methods. Replacing the ! with a . both declares a private attribute and an accessor method named after the attribute. In this case, both the attribute $!done and the accessor method done are declared. It's as if you had written:

      has Bool $!done; method done() { return $!done }

      Note that this is not like declaring a public attribute, as some languages allow; you really get both a private attribute and a method, without having to write the method by hand. You are free instead to write your own accessor method, if at some future point you need to do something more complex than returning the value.

      So I would prefer to declare the set method as method set( :$x = $!x, :$y = $!y ). But, of course, YMMV.

      Yes, that makes more sense to me. The variable's name is $!x (where the ! indicates it is a private variable) and as far as I know you only write $.x when you declare the instance variable in order to generate a read accessor.

      Here is an example I played with:

      use v6; class Point { has $.x = 0; has $.y = 0; method gist { "[$.x, $.y]" } method set( :$joe = $!x, :$susan = $!y ) { $!x = $joe; $!y = $susan; } } my $point = Point.new(); say $point.x; # 0 say $point; # [0, 0] # $point.x = 3; Error! $point = Point.new(x => 3, y => 5); say $point; # [3,5] $point.set(joe => 10, susan => 20); say $point; # [10, 20] $point.set(joe => 100); say $point; # [100, 20]

      Thanks for taking the time to help me. I really appreciate it.

        That's a good question and a good answer. Raku (like perl) has some niceties as you dig in ... but the defaults are set at "easy"

        Consider this...

        use v6; class Point { has $.x is rw = 0; has $.y is rw = 0; method gist { "[$.x, $.y]" } method set( :$joe = $.x, :$susan = $.y ) { #method set( :$joe = $!x, :$susan = $!y ) { $.x = $joe; $.y = $susan; } } class Pixel is Point { has Int $.x is rw = 0; has Int $.y is rw = 0; has $.color = '#000000'; } my $point = Pixel.new(); #my $point = Point.new(); say $point.x; # 0 say $point; # [0, 0] # $point.x = 3; Error! $point = Pixel.new(x => 3, y => 5); #$point = Point.new(x => 3, y => 5); say $point; # [3,5] $point.set(joe => 10, susan => 20); say $point; # [10, 20] $point.set(joe => 100); say $point; # [100, 20]

        The $!x / $!y variant does not work because it directly refers to the PRIVATE ATTR of the Point class. But, in this case, we have used inheritance to override the Point x/y with Pixel x/y. So, when you need to reach for inheritance then the $.x / $.y variants help ... since they are the same as saying self.x and self.y - ie. they route via the settor / getter METHODS.

        We need the 'is rw' to ask raku to make a settor method so that $.x = $joe; $.y = $susan; can use the assignment operation to set attr values

Re: Raku classes: syntax question
by raiph (Deacon) on Feb 06, 2024 at 00:45 UTC
    First and foremost, where is that code from? There are plenty of P6/Raku class point examples around, but the only match google found with a method set in it is this perlmonks post.

    The code appears to be a relatively simple contrived example, but the method set is such a travesty that it's a minor tragedy you've been exposed to it.

    I get that it's somewhat moot now. You found/saw it, have done a bunch of work to try and understand it, and here we are, and you've done fine, so it's not a huge deal.

    But the example is so bad I've concluded there's little point in me commenting much further about it, other than to make clear it's a mess as an example, even as a contrived one. Yes, you've learned from it, and posting it has gotten you helpful comments by Athanasius and steve. No, you should not stick with whatever source it came from, and I recommend you don't even bother to further your understanding of that example.


    Other than that, I want to follow up on one aspect that was touched on by Athanasius, yourself, and steve, but perhaps slightly at odds with each other.

    as far as I know you only write $.x when you declare the instance variable in order to generate a read accessor.

    Imo that's a good thing to think, other than the tweak that has $.x is rw generates a write accessor too as steve has explained. You can and should take that on board. But other than that, please just keep on thinking what you were thinking about the only time to use code of the form $.x being in an attribute declaration.

    In particular, elsewhere within a method in a class containing the attribute $!x, if you want to refer to a field x, please write one of these:

    • $!x, if you mean to refer directly to the field's underlying attribute. This should be what you first think to write. It guarantees you get that specific attribute's value. It may well be the only thing you ever write to get at field x in a method of a class containing an attribute with that name.
    • self.x, if you mean to refer to x indirectly via a call to its accessor method named x. This allows things like sub-classes and mixins to override where the .x method gets dispatched to. (But do you really want that?)

    I'll close with a bit more gnarly detail about this point just in case you care to scare yourself or others.

    As steve has noted, and the doc even suggests, the syntax $.x can currently be used to invoke the accessor method within other methods inside the class. Athanasius even mentions it, saying "Raku allows you to sometimes use $.x as an alias for $!x".

    They're not actually aliases -- $.x calls a method, almost the same as self.x -- so please don't take the "alias" aspect literally. But I agree with Athanasius that you're best off not using $.x outside of attribute declarations.

    And here's why: there are multiple outstanding bugs and quirks related to use of $.foo outside of has $.foo declarations. Such use will sometimes loudly but confusingly, or, worse, silently, do completely the wrong thing.

      First and foremost, where is that code from?

      It's from a youtube video titled "Raku: The Programming Language You Didn't Know You Needed" by someone named Curtis Poe. The description says:

      This is an update of my Perl 6 for Mere Mortals talk. There's not much new, but it refers to Raku instead of Perl 6 and some slides have been cleaned up. This was presented for the 2021 FOSDEM conference.

      Link to Video