13
#Private attribute example
class C { 
    has $!w;                            #private attribute
    multi method w { $!w }              #getter method
    multi method w ( $_ ) {                 #setter method
        warn “Don’t go changing my w!”;   #some side action
        $!w = $_
    }  
}
my $c = C.new
$c.w( 42 )
say $c.w #prints 42
$c.w: 43
say $c.w #prints 43

#but not
$c.w = 44
Cannot modify an immutable Int (43)

so far, so reasonable, and then

#Public attribute example
class C { 
    has $.v is rw    #public attribute with automatic accessors
}
my $c = C.new
$c.v = 42
say $c.v #prints 42

#but not
$c.v( 43 ) #or $c.v: 43
Too many positionals passed; expected 1 argument but got 2

I like the immediacy of the ‘=‘ assignment, but I need the ease of bunging in side actions that multi methods provide. I understand that these are two different worlds, and that they do not mix.

BUT - I do not understand why I can’t just go $c.v( 43 ) To set a public attribute

  1. I feel that raku is guiding me to not mix these two modes - some attributes private and some public and that the pressure is towards the method method (with some : sugar from the colon) - is this the intent of Raku's design?
  2. Am I missing something?
librasteve
  • 6,832
  • 8
  • 30
  • You could use a Proxy – user0721090601 Jan 09 '20 at 21:27
  • How could you use a Proxy? I mean, the accessor already returns a container when `is rw` is specified. Returning a Proxy is not going to change the number of allowable parameters on the accessor. – Elizabeth Mattijsen Jan 09 '20 at 21:38
  • @ElizabethMattijsen Perhaps I misunderstood the question. But this seems to work for what he wants (enabling both `= foo` and `.(foo)` for setting) and enabling side effects to be done in both cases (but not when only fetched): https://tio.run/##ZY9NDoIwEIX3PcUzdgGJslMTCG5Q406jHkDkJ5CAGAqxhnAXz@LFcBrAn/AWTTvz5uubW5An86bxElcIOKgYELkCfCQtuqZlUsRIgyLKfEhoXOpABeoAY6wyiNgPIIoyDNsizcEGl@pVDwGxQH7vAemDjNNLVl59mHb/JbDPM/kwrsHdbH3A8bQ7rGEve5DGqaujej3H2vkvhf5FEpFcqCc9ZbM@OdsfSqcKQ30gnep2n5oxFdojtqMCWoxxz5DaTLeYcFXHoB3USY7FT61p3g – user0721090601 Jan 10 '20 at 00:48

3 Answers3

14

is this the intent of Raku's design?

It's fair to say that Raku isn't entirely unopinionated in this area. Your question touches on two themes in Raku's design, which are both worth a little discussion.

Raku has first-class l-values

Raku makes plentiful use of l-values being a first-class thing. When we write:

has $.x is rw;

The method that is generated is:

method x() is rw { $!x }

The is rw here indicates that the method is returning an l-value - that is, something that can be assigned to. Thus when we write:

$obj.x = 42;

This is not syntactic sugar: it really is a method call, and then the assignment operator being applied to the result of it. This works out, because the method call returns the Scalar container of the attribute, which can then be assigned into. One can use binding to split this into two steps, to see it's not a trivial syntactic transform. For example, this:

my $target := $obj.x;
$target = 42;

Would be assigning to the object attribute. This same mechanism is behind numerous other features, including list assignment. For example, this:

($x, $y) = "foo", "bar";

Works by constructing a List containing the containers $x and $y, and then the assignment operator in this case iterates each side pairwise to do the assignment. This means we can use rw object accessors there:

($obj.x, $obj.y) = "foo", "bar";

And it all just naturally works. This is also the mechanism behind assigning to slices of arrays and hashes.

One can also use Proxy in order to create an l-value container where the behavior of reading and writing it are under your control. Thus, you could put the side-actions into STORE. However...

Raku encourages semantic methods over "setters"

When we describe OO, terms like "encapsulation" and "data hiding" often come up. The key idea here is that the state model inside the object - that is, the way it chooses to represent the data it needs in order to implement its behaviors (the methods) - is free to evolve, for example to handle new requirements. The more complex the object, the more liberating this becomes.

However, getters and setters are methods that have an implicit connection with the state. While we might claim we're achieving data hiding because we're calling a method, not accessing state directly, my experience is that we quickly end up at a place where outside code is making sequences of setter calls to achieve an operation - which is a form of the feature envy anti-pattern. And if we're doing that, it's pretty certain we'll end up with logic outside of the object that does a mix of getter and setter operations to achieve an operation. Really, these operations should have been exposed as methods with a names that describes what is being achieved. This becomes even more important if we're in a concurrent setting; a well-designed object is often fairly easy to protect at the method boundary.

That said, many uses of class are really record/product types: they exist to simply group together a bunch of data items. It's no accident that the . sigil doesn't just generate an accessor, but also:

  • Opts the attribute into being set by the default object initialization logic (that is, a class Point { has $.x; has $.y; } can be instantiated as Point.new(x => 1, y => 2)), and also renders that in the .raku dumping method.
  • Opts the attribute into the default .Capture object, meaning we can use it in destructuring (e.g. sub translated(Point (:$x, :$y)) { ... }).

Which are the things you'd want if you were writing in a more procedural or functional style and using class as a means to define a record type.

The Raku design is not optimized for doing clever things in setters, because that is considered a poor thing to optimize for. It's beyond what's needed for a record type; in some languages we could argue we want to do validation of what's being assigned, but in Raku we can turn to subset types for that. At the same time, if we're really doing an OO design, then we want an API of meaningful behaviors that hides the state model, rather than to be thinking in terms of getters/setters, which tend to lead to a failure to colocate data and behavior, which is much of the point of doing OO anyway.

Jonathan Worthington
  • 29,104
  • 2
  • 97
  • 136
  • Good point on cautioning the `Proxy`s (even though I suggested it ha). The only time I've found them terribly useful is for my `LanguageTag`. Internally, the `$tag.region` returns an object of type `Region` (as it's stored internally), but the reality is, it's infinitely more convenient for people to say `$tag.region = "JP"` over `$tag.region.code = "JP"`. And that's really only temporary until I can express a coercion from `Str` in the type, e.g., `has Region(Str) $.region is rw` (which requieres two separate planned but low priority features) – user0721090601 Jan 10 '20 at 01:56
  • Thank you @Jonathan for taking time to elaborate the design rationale - I had suspected some kind of oil and water aspect and for a non CS bod like me, I really get the distinction between proper OO with hidden state and the application of class es as a friendlier way to build esoteric data holders which would take a PhD in C++. And to user072... for your thoughts on Proxy s. I did know about Proxy s before but suspected that they (and/or traits) are deliberately quite onerous syntax to discourage the mixing of oil and water... – librasteve Jan 10 '20 at 18:34
  • p6steve: Raku was really designed to make the most common/easy things super easy. When you deviate from the common models, it's always possible (I think you've seen about three different ways to do what you want so far...and there's definitely more), but it makes you work a bit — just enough to make sure what you're doing is really want you want. But via traits, proxies, slangs, etc, you can make it so you only need a few extra characters to really enable some cool stuff when you need it. – user0721090601 Jan 10 '20 at 20:48
7

BUT - I do not understand why I can’t just go $c.v( 43 ) To set a public attribute

Well, that's really up to the architect. But seriously, no, that's simply not the standard way Raku works.

Now, it would be entirely possible to create an Attribute trait in module space, something like is settable, that would create an alternate accessor method that would accept a single value to set the value. The problem with doing this in core is, is that I think there are basically 2 camps in the world about the return value of such a mutator: would it return the new value, or the old value?

Please contact me if you're interested in implementing such a trait in module space.

Elizabeth Mattijsen
  • 25,654
  • 3
  • 75
  • 105
  • 1
    Thanks @Elizabeth - this is an interesting angle - I am only hitting this question here and there and there is not enough payback in building a trait to do the trick (or skill on my part). I was really trying to get my head around the best coding practice and align to that - and hoping that Raku's design would be aligned to best practice(s), which I gather it is. – librasteve Jan 10 '20 at 18:40
6

I currently suspect you just got confused.1 Before I touch on that, let's start over with what you're not confused about:

I like the immediacy of the = assignment, but I need the ease of bunging in side actions that multi methods provide. ... I do not understand why I can’t just go $c.v( 43 ) To set a public attribute

You can do all of these things. That is to say you use = assignment, and multi methods, and "just go $c.v( 43 )", all at the same time if you want to:

class C {
  has $!v;
  multi method v                is rw {                  $!v }
  multi method v ( :$trace! )   is rw { say 'trace';     $!v }
  multi method v ( $new-value )       { say 'new-value'; $!v = $new-value }
}
my $c = C.new;
$c.v = 41;
say $c.v;            # 41
$c.v(:trace) = 42;   # trace
say $c.v;            # 42
$c.v(43);            # new-value
say $c.v;            # 43

A possible source of confusion1

Behind the scenes, has $.foo is rw generates an attribute and a single method along the lines of:

has $!foo;
method foo () is rw { $!foo }

The above isn't quite right though. Given the behavior we're seeing, the compiler's autogenerated foo method is somehow being declared in such a way that any new method of the same name silently shadows it.2

So if you want one or more custom methods with the same name as an attribute you must manually replicate the automatically generated method if you wish to retain the behavior it would normally be responsible for.

Footnotes

1 See jnthn's answer for a clear, thorough, authoritative accounting of Raku's opinion about private vs public getters/setters and what it does behind the scenes when you declare public getters/setters (i.e. write has $.foo).

2 If an autogenerated accessor method for an attribute was declared only, then Raku would, I presume, throw an exception if a method with the same name was declared. If it were declared multi, then it should not be shadowed if the new method was also declared multi, and should throw an exception if not. So the autogenerated accessor is being declared with neither only nor multi but instead in some way that allows silent shadowing.

raiph
  • 31,607
  • 3
  • 62
  • 111
  • Aha - thanks @raiph - that's the thing I felt was missing. Now it makes sense. Per Jnthn I will probably try to be a better true OO coder and keep the setter style for pure data containers. But it's good to know that this is in the toolbox! – librasteve Jan 10 '20 at 18:43