Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

A12: default accessors and encapsulation

10 views
Skip to first unread message

John Siracusa

unread,
Apr 19, 2004, 2:20:36 PM4/19/04
to Perl 6 Language
Let's say I have a class with some attributes:

class Dog;

has $.name is rw;
has $.age is rw;
has $.gender is rw;

I initially decide to accept the default accessors.

$dog.name = 'Ralph';
print $dog.age;

This works well for a while, but then I decide to update Dog so that setting
the name also sets the gender.

$dog.name = 'Susie'; # also sets $dog.gender to 'female'

How do I write such a name() method? Do I just check the arg, set the
gender, and then return $.name as an "lvalue" or something?

If so, what happens if, some time down the road, the $.name attribute goes
away entirely? I can't return it as an lvalue now, can I?

Basically, I'm wondering how much of the object's internals I'm exposing by
accepting the default accessors.

-John

Juerd

unread,
Apr 19, 2004, 2:32:54 PM4/19/04
to John Siracusa
John Siracusa skribis 2004-04-19 14:20 (-0400):
> has $.gender is rw;
> (...)

> This works well for a while, but then I decide to update Dog so that setting
> the name also sets the gender.
> $dog.name = 'Susie'; # also sets $dog.gender to 'female'
> How do I write such a name() method? Do I just check the arg, set the
> gender, and then return $.name as an "lvalue" or something?

IIRC, something like

has $.name is rw is STORE { ... };

> If so, what happens if, some time down the road, the $.name attribute goes
> away entirely? I can't return it as an lvalue now, can I?

Why not?


Juerd

Austin Hastings

unread,
Apr 19, 2004, 3:58:41 PM4/19/04
to John Siracusa, Perl 6 Language

> -----Original Message-----
> From: John Siracusa [mailto:sira...@mindspring.com]
> Sent: Monday, 19 April, 2004 02:21 PM
> To: Perl 6 Language
> Subject: A12: default accessors and encapsulation
>
>
> Let's say I have a class with some attributes:
>
> class Dog;
>
> has $.name is rw;
> has $.age is rw;
> has $.gender is rw;
>
> I initially decide to accept the default accessors.
>
> $dog.name = 'Ralph';
> print $dog.age;
>
> This works well for a while, but then I decide to update Dog so
> that setting
> the name also sets the gender.
>
> $dog.name = 'Susie'; # also sets $dog.gender to 'female'
>
> How do I write such a name() method? Do I just check the arg, set the
> gender, and then return $.name as an "lvalue" or something?

class Afghan
is Dog
{
does Hunter;
does AKC;

has $.name is rw
will STORE { .set_name($^name); };

has $.taliban is OppressiveGovernment;

method set_name($self: String $name) {
DESTROY unless $.taliban.approves($name);

given ($name) {
when <Names::Female> {
...
}

...
}
}
}


>
> If so, what happens if, some time down the road, the $.name attribute goes
> away entirely? I can't return it as an lvalue now, can I?

method name(String ?$name) {...}

>
> Basically, I'm wondering how much of the object's internals I'm
> exposing by accepting the default accessors.

Since lvalue subs/methods are easy, and $obj.attr looks like $obj.method
with no args, it should be pretty trivial to drop in a replacement if you
decide you don't like the attribute-based solution.

You are a little committed, since <$dog.name = "fred"> does assume that
you've got a reference to something you can pass back, but that's about it.

You can leave the "name" attribute around as a placeholder and let the STORE
block update the "official" location, or you could return some sort of
proxy-lvalue object that wasn't really a part of Dog:

class WeaselDog
is Dog
{
class PhonyAttr
{
$.store;
$.fetch;

method STORE { $.store($^value); return $_; }
method FETCH { return $.fetch(); }
}

method set_name(String $name) {...}
method get_name() {...}

method name is rw {
return new PhonyAttr(
object => $_,
store => { .set_name $^name; },
fetch => { .get_name; }
);
}
}


=Austin

John Siracusa

unread,
Apr 19, 2004, 4:21:45 PM4/19/04
to Perl 6 Language
On 4/19/04 3:58 PM, Austin Hastings wrote:
>> I initially decide to accept the default accessors.
>>
>> $dog.name = 'Ralph';
>> print $dog.age;
>>
>> This works well for a while, but then I decide to update Dog so that setting
>> the name also sets the gender.
>>
>> $dog.name = 'Susie'; # also sets $dog.gender to 'female'
>>
>> How do I write such a name() method?
>
> has $.name is rw
> will STORE { .set_name($^name); };

The "will STORE" stuff covers the easy cases, but can I extend it all the
way up to a name() that's a multimethod with a ton of optional args? I
supposed you can (technically) do all of that with "will STORE", but it
seems an odd place for what would more naturally be code in the name()
method itself.

> You can leave the "name" attribute around as a placeholder and let the STORE
> block update the "official" location, or you could return some sort of
> proxy-lvalue object that wasn't really a part of Dog

Heh, getting progressively more scary :)

From the point of view of the person coding the new, fancier name() method,
it would be nice if some magic would make all existing calls to

$dog.name = 'foo';

look like this inside the new name() method

$dog.name('foo');

but I supposed that really hoses the meaning of "=" :)

The alternate techniques suggested are powerful, but they also strike me as
slightly heroic. I can imaging using them to patch or extend some existing
code, but starting a Perl 6 class from scratch, I'd really have to think
about the costs of using the default accessors at all.

One work-around might be an alternate kind of default accessor that doesn't
allow assignment:

$dog.name # get
$dog.name('foo') # set
$dog.name = 'foo' # compile-time error

That is a lot more directly (and simply) "future-proof" than the "is rw"
accessor.

I'd either like a way to more cleanly extend the default accessor's
assignment behavior down the road (i.e. by just writing a new name() method,
not by hacking away at STORE traits and adding private worker subs) or a way
to auto-generate the slightly more "boring" default accessor shown above.

I'd prefer the former since I'm hoping to avoid the likes of
Class::MethodMaker as long as possible in the world of Perl 6 :)

In the absence of both, I can imaging that 5 years into the life of 6PAN, a
substantial portion of the Perl 6 modules will have STORE hooks on what they
originally thought would be "simple" attributes that don't need full-blown
accessors... :)

-John

John Siracusa

unread,
Apr 19, 2004, 6:53:29 PM4/19/04
to Perl 6 Language
On 4/19/04 4:47 PM, mark.a...@comcast.net wrote:
>> On 4/19/04 3:58 PM, Austin Hastings wrote:
>> One work-around might be an alternate kind of default accessor that doesn't
>> allow assignment:
>>
>> $dog.name # get
>> $dog.name('foo') # set
>> $dog.name = 'foo' # compile-time error
>
> I think we already have this. Just define a non-rw attribute and then
> add your own writer as a multi-method.
>
> has Str $.name;
> multi method name(Str $n) {$.name = $n;}

Yeah, that's exactly what I don't want to type over and over :) It's not
much better than the old Perl 5 standby:

sub name { @_ > 1 ? $_[0]->{'name'} = $_[1] : $_[0]->{'name'} }

since once I have to write something, the time and effort savings is pretty
much cancelled out.

-John

Luke Palmer

unread,
Apr 20, 2004, 1:25:23 AM4/20/04
to John Siracusa, Perl 6 Language
John Siracusa writes:
> On 4/19/04 3:58 PM, Austin Hastings wrote:
> >> I initially decide to accept the default accessors.
> >>
> >> $dog.name = 'Ralph';
> >> print $dog.age;
> >>
> >> This works well for a while, but then I decide to update Dog so that setting
> >> the name also sets the gender.
> >>
> >> $dog.name = 'Susie'; # also sets $dog.gender to 'female'
> >>
> >> How do I write such a name() method?
> >
> > has $.name is rw
> > will STORE { .set_name($^name); };
>
> The "will STORE" stuff covers the easy cases, but can I extend it all the
> way up to a name() that's a multimethod with a ton of optional args? I
> supposed you can (technically) do all of that with "will STORE", but it
> seems an odd place for what would more naturally be code in the name()
> method itself.

I think a role on the attribute is not the right place to put it. What
you're doing is returning a proxy object that knows how to set both the
name and the gender. Here are a couple of implementations:

class Dog {
has $.name;
has $.gender;

method name() {
return my $dummy
is Proxy(
for => $.name,
STORE => sub ($in) {
$.gender = /<Names::Female>/ ?? 'male' :: 'female';
$.name = $in;
},
);
}
}

Yuck. Much nicer:

class Dog {
has $.name;
has $.gender;

method name()
will get { $.name }
will set -> $in {
$.gender = /<Names::Female>/ ?? 'make' :: 'female';
$.name = $in;
}
{ }
}

This is nothing new. So, for fun, here's the implementation of C<get>
and C<set>:

role get {
multi sub trait_auxiliary:is(get $trait, &code: ?$arg) {
wrap &code: {
my $result := call;
return my $dummy
is Proxy(
for => $result,
FETCH => $arg,
);
};
}
}

role set {
multi sub trait_auxiliary:is(set $trair, &code: ?$arg) {
wrap &code: {
my $result := call;
return my $dummy
is Proxy(
for => $result,
STORE => $arg,
);
};
}
}

Luke

Damian Conway

unread,
Apr 19, 2004, 10:04:02 PM4/19/04
to perl6-l...@perl.org
John Siracusa wrote:

> I'd either like a way to more cleanly extend the default accessor's
> assignment behavior down the road (i.e. by just writing a new name() method,
> not by hacking away at STORE traits and adding private worker subs) or a way
> to auto-generate the slightly more "boring" default accessor shown above.

There *is* a way to do the latter. In A12, Larry implies that the declarators
in the body of a class definition are actually method calls on an instance of
MetaClass:

http://www.perl.com/pub/a/2004/04/16/a12.html?page=3#use_of_classes
http://www.perl.com/pub/a/2004/04/16/a12.html?page=19#new_grammatical_categories

So, presumably, by defining a new C<scope_declarator:has>, you would be able
to override the default accessor-generating behaviour.

The least scary way to do this would be to encapsulate it in a trait that is
applied to (and has its way with ;- the class declaration:

class Dog is getset {
has $.name is rw;
...
}

or perhaps is applied instead to individual attribute declarations:

class Dog {
has $.name is getset;
...
}


Alternatively, you could just *cheat* and define a macro (putting it in a
lexically-scoped module for convenience and safety):

module GetSet;

macro getset is export(:MANDATORY)
is parsed(rx:words/ <?Perl6.type>? <?Perl6.attr_var>/)
($attr)
{
my $accessor = substr $attr{attr_var}, 2;
return "method set_$accessor ($attr{type} $attr{$attr_var}) {} "
~ "method get_$accessor () { return $attr{attr_var} } "
~ "has $attr{type} $attr{attr_var}"
;
}

and then:

class Dog {
use GetSet;
getset $.name;
...
}


Damian

Larry Wall

unread,
Apr 19, 2004, 7:20:47 PM4/19/04
to Perl 6 Language
On Mon, Apr 19, 2004 at 06:53:29PM -0400, John Siracusa wrote:
: Yeah, that's exactly what I don't want to type over and over :)

I really don't understand what you're getting at here. First you
complain that you'd rather write an ordinary method, and then you
complain that you have to. Have I met someone lazier than me? :-)

Larry

Mark A Biggar

unread,
Apr 19, 2004, 4:47:34 PM4/19/04
to John Siracusa, Perl 6 Language
> On 4/19/04 3:58 PM, Austin Hastings wrote:

> One work-around might be an alternate kind of default accessor that doesn't
> allow assignment:
>
> $dog.name # get
> $dog.name('foo') # set
> $dog.name = 'foo' # compile-time error

I think we already have this. Just define a non-rw attribute and then
add your own writer as a multi-method.

has Str $.name;
multi method name(Str $n) {$.name = $n;}

--
Mark Biggar
mark.a...@comcast.net

John Siracusa

unread,
Apr 20, 2004, 10:13:30 AM4/20/04
to Perl 6 Language

Possibly :) Here's what I'm saying. In the first version of a class, there
will probably be a lot of simple get/set attributes. It's convenient not to
have to write any explicit methods for those.

If I accept the default accessors that you get "for free" when a class "has
$.foo is rw", then that means the users of my class can do $obj.foo =
whatever in order to set the foo attribute.

That's fine...until, some day down the road, I decide that getting and/or
setting the foo attribute needs to do something a bit more complex. Or
maybe I remove the $.foo attribute all together and replace it with a
collection of other attributes or something.

I can do that by writing my own foo() method, but there's the problem of all
the existing code out there that says $obj.foo = whatever. In order to
continue to support that, I have to provide an lvalue. As has been pointed
out, I can use "will STORE" to add the "extra" behavior while continuing to
use $.foo (or a proxy object, yadda) as my lvalue. But I find that slightly
kludgy.

If the default accessor had instead been defined like (the Perl 6
equivalent) of this:

sub name { @_ > 1 ? $_[0]->{'foo'} = $_[1] : $_[0]->{'foo'} }

then I wouldn't have any existing code doing $obj.foo = whatever to worry
about. So extending my foo() accessor by writing my own method would be
straight-forward.

Another alternative is to have a simpler, more direct way to continue to
support the $obj.foo = whatever semantics by writing a particular kind of
foo() method. I don't like piling the "side effects" into a "will STORE"
trait, since in version 4.0 of my class there could be a ton of code hanging
off that hook. It just looks like the wrong place to put that. I'd rather
have all the fancy things done when I call $obj.foo() defined in method foo
{ }.

A final alternative is to never use the default accessor that I get when my
class "has $.foo is rw" because that means having to support $obj.foo =
whaever semantics forever and ever. But that means I have to write (the
Perl 6 equivalent of) this:

sub name { @_ > 1 ? $_[0]->{'foo'} = $_[1] : $_[0]->{'foo'} }

for every single attribute. That's tiresome.

Basically, I'm wondering exactly who is the target audience for the default
accessor? It seems to tie the API to a very confining set of behaviors, and
expose more of the internals of a class than I'd like.

If $ob.foo will really never expand beyond a simple get/set interface to an
attribute, it works fine. But that's rarely true in the long term, IME. It
also, I think, creates an artificial distinction between those "simple"
attributes that users can expect to set with $obj.foo = whatever, and the
more complex attributes that must be set with $obj.foo(whatever). I'd like
more symmetry in my APIs, but then I'm faced with the options describe
above, none of which are as appealing as I'd like.

I think others will also value API symmetry and will therefore either create
their own "old-style" accessors everywhere, or (more likely) go to heroic
measures to support $obj.foo = whatever everywhere, forever, probably by
dangling 80% of their attribute accessor methdod code off of "will STORE"
hooks by the time version 4.0 cof their class rolls around. That's not
pleasant, IMO.

-John

John Siracusa

unread,
Apr 20, 2004, 10:19:35 AM4/20/04
to Perl 6 Language
On 4/19/04 10:04 PM, Damian Conway wrote:
> John Siracusa wrote:
>> I'd either like a way to more cleanly extend the default accessor's
>> assignment behavior down the road (i.e. by just writing a new name() method,
>> not by hacking away at STORE traits and adding private worker subs) or a way
>> to auto-generate the slightly more "boring" default accessor shown above.
>
> There *is* a way to do the latter. In A12, Larry implies that the declarators
> in the body of a class definition are actually method calls on an instance of
> MetaClass [...] So, presumably, by defining a new C<scope_declarator:has>, you

> would be able to override the default accessor-generating behaviour.

Ooo, that would be neat.

> The least scary way to do this would be to encapsulate it in a trait that is

> applied to (and has its way with ;- the class declaration [...] or perhaps is


> applied instead to individual attribute declarations

Either of those looks okay...well, maybe "Damian okay" which is a slightly
different standard ;)

> Alternatively, you could just *cheat* and define a macro

That's a bit much for me. I mean, yeah, sure, you can basically do
"anything" that way, but come on. I dread what you will do with (or is that
"to") Perl 6 once it's released... ;)

-John

John Siracusa

unread,
Apr 20, 2004, 10:24:08 AM4/20/04
to Perl 6 Language
On 4/20/04 1:25 AM, Luke Palmer wrote:

> John Siracusa writes:
>> The "will STORE" stuff covers the easy cases, but can I extend it all the
>> way up to a name() that's a multimethod with a ton of optional args? I
>> supposed you can (technically) do all of that with "will STORE", but it
>> seems an odd place for what would more naturally be code in the name()
>> method itself.
>
> I think a role on the attribute is not the right place to put it. What
> you're doing is returning a proxy object that knows how to set both the
> name and the gender.

That's a bit too "example-specific." Really what I was getting at was
arbitrary, simple extensibility to do "anything" in response to $obj.foo
(with and without args) down the road, not just to do the specific thing
that I described in my example.

With a "boring" Perl 5 style get/set accessor API, I can just rewrite method
foo { } until the cows come home. I find that a lot more straight-forward
than the role playing you describe, but YMMV :)

-John

Mark J. Reed

unread,
Apr 20, 2004, 10:40:05 AM4/20/04
to Perl 6 Language
Let me just chime in with my support for John's basic idea. I would
definitely prefer that it be easy to arrange things such that

$obj.foo = 'bar'

winds up invoking a method on $obj with 'bar' as an argument, rather
than invoking a method on $obj that returns an lvalue to which
'bar' is then assigned. (Yes, like Ruby's def foo=; I have a Rubyometer
and I'm not afraid to use it!) It doesn't need to be the default
rw accessor behavior, but I would like it to be accomplishable without
jumping through a lot of hoops.

-Mark

Matthew Walton

unread,
Apr 20, 2004, 11:21:45 AM4/20/04
to Mark J. Reed, Perl 6 Language
Mark J. Reed wrote:

It sounds a lot like C#'s properties, which are in my opinion one of the
best things in C#. Nice easy syntax for those as well, although I can't
remember it well enough right now to give an example, as I don't
generally keep a C# reference lying around at work where I never use it.
Effectively though, if you have an object foo with a property bar,
things like

foo.bar = 5;

seem to get turned into

foo.set_bar(5);

where set_bar() was pulled from the property declaration's set {} block,
where some magic goes on.

However, it would appear that what John is asking for is already
possible, just not necessarily particularly obviously, as returning a
proxy object to act as the lvalue and then do the appropriate magic
would work, but seems a little mucky. Even if that's what happens behind
the scenes.

On the other hand, just as a thought from a crazy C++er, couldn't you
accomplish a similar effect by defining your $.foo attribute to be of a
custom class, then overriding the = operator for that class...

I know, messy messy. Don't go that way. I shouldn't even have thought of it.

Austin Hastings

unread,
Apr 20, 2004, 11:32:46 AM4/20/04
to Mark J. Reed, Perl 6 Language

A problem with

$obj.foo = 'bar';

getting converted to

$obj.set_foo('bar');

(or whatever) is that set_foo may not have lvalue semantics. That is, it may
not behave appropriately by returning an lvalue object for use in cascading
assignment constructs:

$obj.foo = $obj.baz = 'bar';

Hanging the behavior off C<will STORE> and/or C<will FETCH> has the benefit
of P6 doing some or all of the work for you.

has $.foo will STORE { .:set_foo($^value); }

submethod :set_foo($new_foo) {...}

...

$obj.foo = 'bar';

A better solution to your scenario is to 6PAN a new MetaClass that does what
you want:

use AccessorClasses;

accessor_class Object
{
has $.foo is rw;

method :set_foo($new_foo, ?$optional_extra) {...}
method :get_foo() {...}
}

A simple, easily bundled grammar mod that automatically generates the C<will
STORE> logic for you, provided that you follow a simple xxx_ method naming
convention.

=Austin

Luke Palmer

unread,
Apr 20, 2004, 12:14:28 PM4/20/04
to Mark J. Reed, Perl 6 Language

Okay, well, I thought that my example did that, but apparently using
C<will get> and C<will set> is a little too complex... (my sentiments
are beginning to follow Larry's, in that I'm not sure you know what you
want -- perhaps you could give a hypotheical syntax?)

So, what you really want is a way so that:

$obj.foo = 'bar';

Is translated into:

$obj.foo('bar');

When you get rid of C<$.foo> and replace it with a C<foo> method.

This is doable (without a syntax munge), but it requires some
cleverness. First I'll do it by hand, then I'll wrap it up in a trait.
Please realize that all this I'm doing is an *implementation* of a very
easy usage.

class Dog {
class :FooProxy {
has $.method;
multi sub infix:= (FooProxy $proxy, Any $val) {
$.method($val);
}
}
method foo_call (?$arg) {
# accessor code here
}
method foo ($self:) is rw {
FooProxy.new(method => { $self.foo_call($_) })
}
}

That should turn:

$obj.foo = 'bar';

Into:

$obj.foo('bar');

And now we want to wrap that up so we can just say C<is accessor>:

role accessor {
class :Proxy {
has $.method;
multi sub infix:= (Proxy $proxy, Any $val) {
$.method($val);
}
}
multi sub trait_auxiliary:is (accessor $a, &code is rw: ?$arg) {
my sub access (::_ $self:) {
Proxy.new(method => { code($self: $_) })
}
&code = &access;
}
}

There. Now here's the important part: in order to *use* all this, you
import whatever module defines it, and then say:

class Dog {
method foo (?$arg) is accessor {
# accessor code here
}
}

If that's not easy enough for you, well, you're probably out of luck.

Luke

John Williams

unread,
Apr 20, 2004, 12:42:21 PM4/20/04
to Luke Palmer, Mark J. Reed, Perl 6 Language
On Tue, 20 Apr 2004, Luke Palmer wrote:
> There. Now here's the important part: in order to *use* all this, you
> import whatever module defines it, and then say:
>
> class Dog {
> method foo (?$arg) is accessor {
> # accessor code here
> }
> }
>
> If that's not easy enough for you, well, you're probably out of luck.

It would be even easier if we could put the read-accessor-code and
write-accessor-code in different methods.

class Dog {
multi method foo { ... }
multi method foo ($arg) is accessor { ... }
}

With this syntax, either method could be simply be omitted to create a
read-only or write-only accessor. The tricky part is getting the trait
on foo($) to override foo() correctly.

~ John Williams


Luke Palmer

unread,
Apr 20, 2004, 12:51:47 PM4/20/04
to John Williams, Mark J. Reed, Perl 6 Language
John Williams writes:
> On Tue, 20 Apr 2004, Luke Palmer wrote:
> > There. Now here's the important part: in order to *use* all this, you
> > import whatever module defines it, and then say:
> >
> > class Dog {
> > method foo (?$arg) is accessor {
> > # accessor code here
> > }
> > }
> >
> > If that's not easy enough for you, well, you're probably out of luck.
>
> It would be even easier if we could put the read-accessor-code and
> write-accessor-code in different methods.
>
> class Dog {
> multi method foo { ... }
> multi method foo ($arg) is accessor { ... }
> }

Ugh! That's what I originally suggested, and it was shot down it was.

My first solution to your problem introduced the traits C<get> and
C<set>, allowing you to write it like this:

class Dog {
method foo ()
will get { ... }
will set { ... }
{ } # usually empty
}

I guess I bogged down that message with the implementation, so the
result may have been easy to miss.

Luke

Mark J. Reed

unread,
Apr 20, 2004, 1:03:16 PM4/20/04
to Perl 6 Language

On 2004-04-20 at 10:51:47, Luke Palmer wrote:
> I guess I bogged down that message with the implementation, so the
> result may have been easy to miss.

That is what happened in my case. Apologies; it looks like your
original solution would do the job nicely. As long as the requisite
module comes standard I'd be satisfied. :)

--
Mark REED | CNN Internet Technology
1 CNN Center Rm SW0831G | mark...@cnn.com
Atlanta, GA 30348 USA | +1 404 827 4754

John Siracusa

unread,
Apr 20, 2004, 1:15:24 PM4/20/04
to Perl 6 Language
On 4/20/04 12:14 PM, Luke Palmer wrote:
> Okay, well, I thought that my example did that, but apparently using
> C<will get> and C<will set> is a little too complex... (my sentiments
> are beginning to follow Larry's, in that I'm not sure you know what you
> want -- perhaps you could give a hypotheical syntax?)

There are a couple of solutions, each with some syntax. Here they are, in
no particular order:

* Provide a second kind of default accessor

class Foo;

has $.foo is accessor; # or whatever, pick a name

With that "has" line alone, you auto-magically get an accessor that works
like this:

$obj.foo # get value of $.foo
$obj.foo(5) # set $.foo = 5

Why this is useful: It doesn't require you to write anything other than the
"has" line, and it provides a clean, simple way to extend the foo() accessor
later in the life of the class by actually writing a foo() method. Unlike
the "is rw" default accessor, there's no fear that any code will exist that
says $obj.foo = whatever.

I also think it's a more sensible default than the "is rw" semantics
(*unless* the solution below is implemented). Training people to use the
default "is rw" accessors only to have to explain later about all the hoops
they will have to jump through in order to maintain that API when the foo
method gets more complex is not my idea of a good time.

* Provide a simple(r) way to easily support the $obj.foo = whatever behavior
in perpetuity for any method.

There are a few ways this could be done. One suggestion was to somehow
appropriately remap this:

$obj.foo = 5;

to this:

$obj.foo(5);

once foo() mutates from an "is rw" default accessor to a full-blown "method
foo(...) { }"

I briefly entertained this solution in one of my earlier posts, but I think
it's not a good idea. It's a good description of the type of behavior I
want, but I don't like futzing with infix:= in order to get it.

Another solution is to have a way to tell method foo() to yank its arg out
of the rvalue if called as an lvalue. I don't have any good ideas about
this syntax, but here's the overview:

# Version 1.0

class Foo;

has $.foo is rw;
...

$obj.foo; # get
$obj.foo = 5; # set

Then:

# Version 4.0

class Foo;

has $.foo; # or maybe $.bar or anything else

method foo(?$arg)
{
# The magic happens in the line below
$arg = the rvalue if(caller wants lvalue);

# Do all sorts of crazy stuff, possibly involving $.foo, possibly not
...

return something; # maybe $.foo, maybe not
}

$obj.foo; # get
$obj.foo(5); # set
$obj.foo = 5; # still works, equivalent to $obj.foo(5)

All the code for maintaining the ability to do $obj.foo = whatever is in the
foo() method itself. It's not hanging off the $.foo attribute, and there
may no longer even be a $.foo attribute anymore. The fact that a $.foo
attribute existed at all was an implementation detail in version 1.0 of
class Foo, IMO.

I think all of these solutions are simpler and cleaner than the STORE hooks,
proxy objects, roles, and macros that have been suggested (all of which seem
like they'd work, FWIW).

-John

John Williams

unread,
Apr 20, 2004, 2:10:28 PM4/20/04
to Luke Palmer, Perl 6 Language

Well, I think we've come full circle. The get/set is the same as the
presumed current implementation, just with different keywords:

class Dog {
has $.foo
will FETCH { ... }
will STORE { ... }
;
}

I'm not saying there is anything wrong with that, but John Siracusa is
asking for something different, I think. A simple accessor which looks
like a method without having to play with Proxy, FETCH, STORE, etc.
If it still looks like $obj.foo = 1 outside the class, that's good too.
(Delphi and C# can do it; why can't we?)

Getting the multi-methods foo()/foo($) to automagically act as the
reader/writer for a virtual foo attibute seems like it would fulfill
his requirements. foo() already works for the reader, but foo($) needs
some help to make $obj.foo = 1 call $obj.foo(1).

Here's a feeble attempt to do what I suggested:

role accessor {
has $.accessor = 1;
multi sub trait_auxiliary:is( accessor $trait, &meth(Any) ) {
my $class = &meth.meta.class;
class $class is extended {
# class methods take precedence, so install a method
# in the class "is default" so it gets called first
multi method $meth.meta.name () is default {
return my $proxy is Proxy (
STORE => { &meth($^val) },
FETCH => {
my ($reader) =
grep !$_.meta.default ,
$class.meta.getmethods;
&$reader;
},
);
}
}
$meth does accessor;
}
}

class Dog {
multi method foo { ... }
multi method foo ($arg) is accessor { ... }
}

my Dog $spot;
$spot.foo++;

~ John Williams


Larry Wall

unread,
Apr 20, 2004, 2:37:18 PM4/20/04
to Perl 6 Language
On Tue, Apr 20, 2004 at 01:15:24PM -0400, John Siracusa wrote:
: With that "has" line alone, you auto-magically get an accessor that works

: like this:
:
: $obj.foo # get value of $.foo
: $obj.foo(5) # set $.foo = 5

I don't care what syntactic sugar you put underneath, but if you expose
this interface to the user, it's fundamentally flawed. This is my
argument from the Apocalypse, and it hasn't changed. It's wrong
to introduce a fundamental asymmetry that breaks the contract that
an accessor can be used as a variable. The arguments to an accessor
should function as part of the "long name" of whatever it is you're
setting, whether it's concrete or abstract. Some of those arguments
may want to be optional, or have other weirdnesses that get introduced
as your interface develops. Having an extra argument on the end
just totally screws up that sort of extensibility.

So do whatever you like to the declarations, but make sure you preserve
the symmetry and extensibility of

$obj.foo(*@STUFF, *%NONSENSE) # get value of $.foo
$obj.foo(*@STUFF, *%NONSENSE) = 5 # set $.foo = 5

And while you're at it, make sure your syntactic sugar doesn't force
people to duplicate the code to process *@STUFF and *%NONSENSE in
their getter and setter.

Them's the specs.

Larry

John Siracusa

unread,
Apr 20, 2004, 3:40:14 PM4/20/04
to Perl 6 Language
On 4/20/04 2:37 PM, Larry Wall wrote:
> On Tue, Apr 20, 2004 at 01:15:24PM -0400, John Siracusa wrote:
> : With that "has" line alone, you auto-magically get an accessor that works
> : like this:
> :
> : $obj.foo # get value of $.foo
> : $obj.foo(5) # set $.foo = 5
>
> I don't care what syntactic sugar you put underneath, but if you expose
> this interface to the user, it's fundamentally flawed. This is my
> argument from the Apocalypse, and it hasn't changed. It's wrong
> to introduce a fundamental asymmetry that breaks the contract that
> an accessor can be used as a variable.

Er, I think we have different definitions of "accessor." I'm perfectly
happy to never allow anyone to do $obj.foo = whatever. I just don't want to
write trivial methods that get and set an attribute behind the scenes.

-John

Mark J. Reed

unread,
Apr 20, 2004, 4:04:42 PM4/20/04
to Perl 6 Language

On 2004-04-20 at 11:37:18, Larry Wall wrote:
> So do whatever you like to the declarations, but make sure you preserve
> the symmetry and extensibility of
>
> $obj.foo(*@STUFF, *%NONSENSE) # get value of $.foo
> $obj.foo(*@STUFF, *%NONSENSE) = 5 # set $.foo = 5
>
> And while you're at it, make sure your syntactic sugar doesn't force
> people to duplicate the code to process *@STUFF and *%NONSENSE in
> their getter and setter.

Works for me. I just want to make sure that it's easy to write classes
that preserve that interface when there *is* no $.foo within the
implementation of the class. After all, the point of abstraction is to
hide implementation details, so it shouldn't matter to the caller whether
or not the attribute exists. And the fact that it looks like it exists to
the caller shouldn't force the implementation to manufacture attributes
just because otherwise there's no lvalue to return for assignment purposes.

As a trivial example, imagine that I want a class Length that lets me do
conversions:

my $len = new Length(furlongs => 10)
say $len.feet ==> 6600.0132
$1en.feet = 6
say $len.meters ==> 1.8288
say $len.furlongs ==> 0.0090908909

Ideally, the implementation of this class should consist of a single
attribute to store the length (in whatever it considers to be the
canonical units), a conversion table, and a single
AUTOLOAD/method_missing/whatever method which does conversions based on
(a) the name by which its invoked and (b) whether it's invoked as an lvalue or
rvalue.

-Mark

Aaron Sherman

unread,
Apr 20, 2004, 4:08:28 PM4/20/04
to John Siracusa, Perl 6 Language

Eh? Why would you never allow anyone to use a simple syntax?

$oldfoo = $obj.foo;
$obj.foo = 1;

What's the problem here? No having to write trivial methods, no
complexity.

What Larry's trying to get at is that:

$status = $thread_shared_data.status(lock=>1);

could be defined as a multi which gets the value of the status variable,
but also asserts that thread synchronization or some form of threadsafe
locking must be performed. You're not SETTING status, you're reading it,
but you are passing parameters to the read accessor. How do you do that
if parameters force a write?

--
Aaron Sherman <a...@ajs.com>
Senior Systems Engineer and Toolsmith
"It's the sound of a satellite saying, 'get me down!'" -Shriekback


Brent 'Dax' Royal-Gordon

unread,
Apr 20, 2004, 3:58:20 PM4/20/04
to John Williams, Luke Palmer, Perl 6 Language
John Williams wrote:
> class Dog {
> has $.foo
> will FETCH { ... }
> will STORE { ... }
> ;
> }
>
> I'm not saying there is anything wrong with that, but John Siracusa is
> asking for something different, I think. A simple accessor which looks
> like a method without having to play with Proxy, FETCH, STORE, etc.
> If it still looks like $obj.foo = 1 outside the class, that's good too.
> (Delphi and C# can do it; why can't we?)

C# does it as

public int buffersize {
get {
return my_buffer.length();
}
set {
//[1]
double sqrt=Math.sqrt(value); //value is a keyword in C#
// because of this feature.
if(Math.floor(sqrt) == sqrt) {
my_buffer=new byte[value];
}
else {
throw new InvalidArgumentException(
"Value is not a power of two"
);
}
}
}

How different is that from:

method buffersize()
will fetch { +$.buffer.bytes }
will store {
my $sqrt=$^v.sqrt;
die "$^v is not a power of two" unless int($sqrt) == $sqrt;
$.buffer = "\x[0]" x $^v;
}
{}

Or even (my suggestion):

method buffersize()
will store {
my $sqrt=$^v.sqrt;
die "$^v is not a power of two" unless int($sqrt) == $sqrt;
$.buffer = "\x[0]" x $^v;
}
{ +$.buffer.bytes }

?

Of course, the best way to implement that would probably be something like:

our type Buffer ::= ByteString where
{ int sqrt +.bytes == sqrt +.bytes };
has Buffer $.buffer;

But that's neither here nor there...


[1] I'm certain there's a more efficient way to do this test, probably
involving bit twiddling. Whatever.

--
Brent "Dax" Royal-Gordon <br...@brentdax.com>
Perl and Parrot hacker

Oceania has always been at war with Eastasia.

John Siracusa

unread,
Apr 20, 2004, 4:20:04 PM4/20/04
to Perl 6 Language
On 4/20/04 4:08 PM, Aaron Sherman wrote:
> On Tue, 2004-04-20 at 15:40, John Siracusa wrote:
>> On 4/20/04 2:37 PM, Larry Wall wrote:
>>> It's wrong to introduce a fundamental asymmetry that breaks the contract
>>> that an accessor can be used as a variable.
>>
>> Er, I think we have different definitions of "accessor." I'm perfectly
>> happy to never allow anyone to do $obj.foo = whatever. I just don't want to
>> write trivial methods that get and set an attribute behind the scenes.
>
> Eh? Why would you never allow anyone to use a simple syntax?
>
> $oldfoo = $obj.foo;
> $obj.foo = 1;
>
> What's the problem here?

The problem is when foo() becomes a complex method some time in the future.
It's some work to "upgrade" foo from a simple "is rw" attribute to a
full-blown method while also maintaining the end user's ability to say
$obj.foo = whatever.

> What Larry's trying to get at is that:
>
> $status = $thread_shared_data.status(lock=>1);
>
> could be defined as a multi which gets the value of the status variable,
> but also asserts that thread synchronization or some form of threadsafe
> locking must be performed. You're not SETTING status, you're reading it,
> but you are passing parameters to the read accessor. How do you do that
> if parameters force a write?

If that's your situation, you can make status() a "getter" only and add a
set_status() "setter." Or you could make foo() say "if I get a single
scalar (i.e. non-pair) arg, then set, otherwise get with options." But
maybe that's as evil to some people as writing trivial get/set accessors is
to me :)

-John

John Williams

unread,
Apr 20, 2004, 5:38:01 PM4/20/04
to Brent 'Dax' Royal-Gordon, Luke Palmer, Perl 6 Language
On Tue, 20 Apr 2004, Brent 'Dax' Royal-Gordon wrote:
> John Williams wrote:
> > I'm not saying there is anything wrong with that, but John Siracusa is
> > asking for something different, I think. A simple accessor which looks
> > like a method without having to play with Proxy, FETCH, STORE, etc.
> > If it still looks like $obj.foo = 1 outside the class, that's good too.
> > (Delphi and C# can do it; why can't we?)
>
> C# does it as
> ...

Interesting. Here's the Delphi version, for comparison.

// the trivial attibute
SomeNumber : Integer;

// the virtual property
// read/write can refer to either a variable or a func/proc
property VirtWrite : Integer read SomeNumber write SetSomeNumber;

procedure SetSomeNumber( in : Integer );
function GetSomeNumber : Integer;

The big win being that one can replace the trivial attribute with a
property, and the interface stays the same.

The perl6 version of that would of course be

class Dog {
method getfoo returns Int {...};
method setfoo(Int $v) {...};

has Int $.foo will FETCH \&getfoo
will STORE \&setfoo;
}

I'm not saying there is anything wrong with that. I completely agree with
Larry that the correct interface for accessors is $x = $obj.foo = $y;

I'm mainly curious if there is an even simpler way to define the
implementation of the accessor than the above.

The simplest possible rw accessor is

method foo is rw { $.foo };

which basically just makes $.foo available to the outside. When you get
more complex and need to intercept the reading and writing, it's clear
that returning a Proxy object is the best way (so far proposed) to do the
right thing for ($x = $obj.foo = $y) and ($obj.foo++). For convenience
and to avoid being uncool-retro, one also wants to separate the accessor
into read and write multi methods.

multi method foo() returns Int {...};
multi method foo(Int $v) {...};

has Int $.foo will FETCH { .foo } # ignore the name conflict for now
will STORE { .foo($^value) };

The declaration of the "has" variable to delegate to the above methods now
becomes repetitive boilerplate code, which could be eliminated by a clever
trait. It also interferes with the obvious name for a multi method
reader.

foo() obviously works fine (by itself) as a reader, but foo($) needs some
help. Somehow foo() has to return a Proxy which delegates STOREs to
foo($), and FETCHes to itself. Since foo() works fine as a read-only
accessor by itself, a minimalist approach shouldn't require foo() to add a
trait. foo($) needs the help so it should get the trait.

So I propose (in the request-for-comments sense, not the "this should
go into perl6 core" sense) that given a trivial accessor:

has $.foo is rw;

the minimal replacement with full read/write control would be this:

multi method foo() {...};
multi method foo($v) is accessor {...};

(And the accessor trait somehow does the magic to define a new foo() which
returns a Proxy which delegates to the declared foo() or foo($).)

~ John Williams


Juerd

unread,
Apr 20, 2004, 4:25:04 PM4/20/04
to Brent 'Dax' Royal-Gordon, perl6-l...@perl.org
Brent 'Dax' Royal-Gordon skribis 2004-04-20 12:58 (-0700):

> method buffersize()
> will store {
> my $sqrt=$^v.sqrt;
> die "$^v is not a power of two" unless int($sqrt) == $sqrt;
> $.buffer = "\x[0]" x $^v;
> }
> { +$.buffer.bytes }

Could this be written as:

method buffersize {
+$.buffer.bytes
} will store {
my $sqrt = $^v.sqrt;
...
}

Or does "will store BLOCK" really have to go before the main block?


Juerd

John Williams

unread,
Apr 20, 2004, 5:41:44 PM4/20/04
to Juerd, Brent 'Dax' Royal-Gordon, perl6-l...@perl.org

The syntax <rule>s in Apocalypse 6 require the main block to be at the
end. http://dev.perl.org/perl6/apocalypse/A06.html#The_sub_form

Jonathan Scott Duff

unread,
Apr 20, 2004, 5:43:38 PM4/20/04
to Juerd, Brent 'Dax' Royal-Gordon, perl6-l...@perl.org
On Tue, Apr 20, 2004 at 10:25:04PM +0200, Juerd wrote:
> Brent 'Dax' Royal-Gordon skribis 2004-04-20 12:58 (-0700):
> > method buffersize()
> > will store {
> > my $sqrt=$^v.sqrt;
> > die "$^v is not a power of two" unless int($sqrt) == $sqrt;
> > $.buffer = "\x[0]" x $^v;
> > }
> > { +$.buffer.bytes }
>
> Could this be written as:
>
> method buffersize {
> +$.buffer.bytes
> } will store {
> my $sqrt = $^v.sqrt;
> ...
> }

Sure

method buffersize
will do { +$.buffer.bytes }
will store { ... }

IIRC

-Scott
--
Jonathan Scott Duff Division of Nearshore Research
du...@lighthouse.tamucc.edu Senior Systems Analyst II

Aaron Sherman

unread,
Apr 23, 2004, 3:12:09 PM4/23/04
to John Siracusa, Perl 6 Language
On Tue, 2004-04-20 at 10:13, John Siracusa wrote:
> On 4/19/04 7:20 PM, Larry Wall wrote:
> > On Mon, Apr 19, 2004 at 06:53:29PM -0400, John Siracusa wrote:
> > : Yeah, that's exactly what I don't want to type over and over :)
> >
> > I really don't understand what you're getting at here. First you
> > complain that you'd rather write an ordinary method, and then you
> > complain that you have to. Have I met someone lazier than me? :-)
>
> Possibly :) Here's what I'm saying. In the first version of a class, there
> will probably be a lot of simple get/set attributes. It's convenient not to
> have to write any explicit methods for those.
>
> If I accept the default accessors that you get "for free" when a class "has
> $.foo is rw", then that means the users of my class can do $obj.foo =
> whatever in order to set the foo attribute.

And if you override the accessor, you can:

multi method foo(Str $blah = undef) is rw($new) {
(my($old),$.foo)=($.foo,$blah//$new);
.update_the_world_in_some_cool_way();
return $old
}

and while that's verbose, it's only a bit more verbose than:

multi method foo(Str $blah) {
(my($old),$.foo)=($.foo,$blah);
return $old;
}

which is what you would have write ANYWAY to add your new functionality.

Austin Hastings

unread,
Apr 23, 2004, 3:23:09 PM4/23/04
to Aaron Sherman, John Siracusa, Perl 6 Language

I don't understand this.

What's the $new doing?

And if you've only got one of them, do you need multi, or just an optional
argument?

method foo(Str ?$new) is rw {
my $old = $.foo;
if (defined($new)) {
$.foo = $new;
.update_the_world();
}
return $old;
}

Larry Wall

unread,
Apr 23, 2004, 4:27:53 PM4/23/04
to Perl 6 Language
On Fri, Apr 23, 2004 at 03:23:09PM -0400, Austin Hastings wrote:
: > And if you override the accessor, you can:

: >
: > multi method foo(Str $blah = undef) is rw($new) {
: > (my($old),$.foo)=($.foo,$blah//$new);
: > .update_the_world_in_some_cool_way();
: > return $old
: > }
:
: I don't understand this.

It's what we on the cabal have been calling an out-of-band parameter.
We have at times contemplated them for various purposes, none of
which seem to have panned out. Instead we've ended up with things
like $CALLER::_ and such.

: What's the $new doing?

It looks to me like it's being a parameter that doesn't interfere
with the official signature.

: And if you've only got one of them, do you need multi, or just an optional
: argument?

It's just an optional argument, but...

: method foo(Str ?$new) is rw {


: my $old = $.foo;
: if (defined($new)) {
: $.foo = $new;
: .update_the_world();
: }
: return $old;

: }

...but it doesn't ruin the signature symmetry between getter and
setter like yours. It does share the misfeature that you have to
write special switching code internally to deal with it, which the
proxy solution automatically dispatches for you. It also assumes
you even want the old value, which you often don't, if the proxy is
destined to be the left side of an ordinary assignment in void context.
And, in fact, if it weren't in void context, you'd want the new value,
not the old one, which means you better hang onto that proxy...

I suppose in the cases where the attribute can serve as its own proxy,
we could have a hook that captures control on setting, and lets you
add side effects en passant without making you responsible to actually
set the variable like a STORE hook would. If you wanted to remove the
actual attribute you'd have to graduate it to a STORE closure on a
real proxy though (with whatever syntactic sugar makes that most
palatable).

Larry

0 new messages