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

Thinking about Accessors

4 views
Skip to first unread message

John Williams

unread,
Feb 27, 2004, 9:37:57 PM2/27/04
to perl6-l...@perl.org
This may all be explained in the upcoming A12, but I'm trying to get
Accessors figured out in my head, and I had a few questions and comments.


===== paraphrased from Damian in http://www.nntp.perl.org/group/perl.perl6.language/9576)

it seems very likely that if you write:

class Foo { # version 1
has $.bar_attr is public;
}

then you'll get an automagically created lvalue accessor method that will
allow you to write:

my $foo = Foo.new();
$foo.bar_attr = 1;

and then later define a real C<Foo::bar_attr> method to encapsulate it:

class Foo { # version 2
has $.bar_attr is public;
method bar_attr is rw($rvalue) () {
if exists $rvalue {
croak "Negative value" if $rvalue < 0;
$.bar_attr = $rvalue;
}
return $.bar_attr;
}
}

=====

From Damian's comments I conclude:

There will be one autogenerated accessor, with the same name as the
attribute (not get/set pairs).

It will work correctly in all these cases:

$x = $foo.bar_attr; # read
$foo.bar_attr = $x; # write
$x = $foo.bar_attr = $y; # write then read

My question is about these cases:

$foo.bar_attr += $z # read then write
$foo.bar_attr++;

L<Apocalypse 6/Lvalue subroutines> would seem to apply here because of the
"is rw" trait. It says I will need to return a proxy object in order for
these cases to work right, because I only get to return from the accessor
once, and subsequent writes are performed on the proxy object.

In fact, I think Damian's example above (written long before A6, so not
his fault) would fail to croak if I wrote:

$foo.bar_attr = 12;
$foo.bar_attr -= 100;

According to A6, it should probably be written like this:

method bar_attr() is rw {
return my $bar_proxy is Proxy(
for => $.bar_attr,
STORE => {
croak "Negative value" if $_ < 0;
$.bar_attr = $_;
},
);
}

But if we *always* have to return a proxy object, in case someone uses +=
on our attribute, I can see users yearning for the simplicity of get/set,
even if it is not as syntactically sweet.

I really like the "is Proxy" for tie-ing things, but I keep thinking its a
bit of overkill for accessors. From an easy-things-should-be-easy
perspective, I wouldn't mind at all if $object.attr += 3 magically
arranged to call an accessor twice: once to read, and once to write.

The accessor would have a simple signature:

method bar(?$val) {...}

It is simple to know if the accessor is reading or writing:

method bar(?$val) {
print "Ima reader" if want;
print "Ima writer" if exists $val;
}

But I am asking for magic with that, because $foo.bar is no longer what it
looks like (a method with no arguments). "$foo.bar += 3" is transformed
into "$for.bar($foo.bar() + 3)". I may lose things like the ability to
bind, etc.

A proxy should be able to do anything, it just doesn't do it as simply as
I want. So it occurs to me that it might be possible to implement the one
in terms of the other. To do that I need to explore another area from the
upcoming apocalypse (traits), so forgive my deficient syntax.

I want to get from here

method bar_attr(?$val) is accessor {
$.bar_attr = $val if exists $val;
return $.bar_attr;
}

to here

method bar_attr() is rw {
return my $x is Proxy (
for => $.bar_attr,
FETCH => { $.bar_attr },
STORE => { $.bar_attr = $_ },
);
}

using a break-the-rules trait called accessor.

I'm not sure what method gets called when a trait is applied to something,
so I will assume that APPLY is called with the something it is applied to
as the invocant.

trait accessor {
my $proxy is Proxy;

method APPLY( Code $acc : ) {
# "Proxy" is a trait/role with FETCH and STORE attributes (?)
$proxy.FETCH = { $acc() };
$proxy.STORE = { $acc($^a) };
# Proxy.for is not set, since I do not know what is being
# proxied; I only know how to access it.

# add the rw trait
$acc but= rw;

# wrap the applyee
$acc.wrap( sub (?$val) {
$proxy = $val if exists $val; # so $foo.bar(1) still works
return $proxy;
} );
}
}

I *think* that accomplishes my goal of simple-to-write accessors (for
scalars, at least). Now I only have to change the signature in Damian's
example, and I think it will work for $foo.bar_attr -= 9999999;

method bar_attr(?$rvalue) is accessor {
if exists $rvalue {
croak "Negative value" if $rvalue < 0;
$.bar_attr = $rvalue;
}
return $.bar_attr;
}

Or maybe I'm missing the point completely.... comments? rebuttals?

~ John Williams


Luke Palmer

unread,
Feb 27, 2004, 9:57:05 PM2/27/04
to John Williams, perl6-l...@perl.org
John Williams writes:
> I want to get from here
>
> method bar_attr(?$val) is accessor {
> $.bar_attr = $val if exists $val;
> return $.bar_attr;
> }
>
> to here
>
> method bar_attr() is rw {
> return my $x is Proxy (
> for => $.bar_attr,
> FETCH => { $.bar_attr },
> STORE => { $.bar_attr = $_ },
> );
> }
>
> using a break-the-rules trait called accessor.

I think this is a good idea. Proxies are a little advanced for someone
who, say, comes from C# and doesn't understand tying yet. I think
you're going about it wrong, though. Here's how I imagine this working:

method bar_attr() will access -> $self: ?$val {


$.bar_attr = $val if exists $val;
return $.bar_attr;
}

Or, alternatively:

method bar_attr() will get { $.bar_attr }
will set { $.bar_attr = $_ }

And the traits C<access>, C<get>, and C<set> define C<do> to do the
right thing. The only trickyness that I see is getting C<get> and
C<set> to work together, since they each need to define part of the same
proxy class.

> I'm not sure what method gets called when a trait is applied to something,
> so I will assume that APPLY is called with the something it is applied to
> as the invocant.
>
> trait accessor {
> my $proxy is Proxy;
>
> method APPLY( Code $acc : ) {

I don't think that's right. That means, to apply the trait, you call
this way:

&bar_attr.accessor::APPLY;

Where I would think it would be something more like:

accessor.APPLY(&bar_attr);

Implying a signature like:

method APPLY(Code $acc is rw) {...}

> # "Proxy" is a trait/role with FETCH and STORE attributes (?)
> $proxy.FETCH = { $acc() };
> $proxy.STORE = { $acc($^a) };
> # Proxy.for is not set, since I do not know what is being
> # proxied; I only know how to access it.
>
> # add the rw trait
> $acc but= rw;

C<rw> is a trait, not a role. So you can't C<but> it on. Probably.

> # wrap the applyee
> $acc.wrap( sub (?$val) {
> $proxy = $val if exists $val; # so $foo.bar(1) still works
> return $proxy;
> } );
> }
> }

That looks okay to me, little as I know about traits. I'm a little
uneasy about declaring the Proxy as a lexical in trait scope; I'd be
inclined to declare it as a lexical in the C<APPLY> routine, or even
in the anonymous sub that you're wrapping with.

Luke

John Williams

unread,
Feb 27, 2004, 11:27:20 PM2/27/04
to Luke Palmer, perl6-l...@perl.org
On Fri, 27 Feb 2004, Luke Palmer wrote:
> John Williams writes:
> > I want to get from here
> >
> > method bar_attr(?$val) is accessor {
> > $.bar_attr = $val if exists $val;
> > return $.bar_attr;
> > }
> >
> I think this is a good idea. Proxies are a little advanced for someone
> who, say, comes from C# and doesn't understand tying yet. I think
> you're going about it wrong, though. Here's how I imagine this working:
>
> method bar_attr() will access -> $self: ?$val {
> $.bar_attr = $val if exists $val;
> return $.bar_attr;
> }

Well, I like being able to just say "is accessor" better...

> Or, alternatively:
>
> method bar_attr() will get { $.bar_attr }
> will set { $.bar_attr = $_ }

... but this is good. I'd forgotten about C<do>.

> And the traits C<access>, C<get>, and C<set> define C<do> to do the
> right thing. The only trickyness that I see is getting C<get> and
> C<set> to work together, since they each need to define part of the same
> proxy class.

It's just setting the FETCH and STORE attributes on a shared Proxy
class, if I understood that right.
Maybe the traits put a Proxy property on the method they are applied to,
and all modify that same Proxy property.

Then, if the get and set traits are just mixed-in with the Method class,
$self for the trait would refer to the Method itself, so they can just
define C<do> to return the Method's Proxy property.

# create the Proxy property
$self but= Proxy.new();

# add the parts defined by traits
$self.Proxy.STORE = $self.set if exists $self.set;
$self.Proxy.FETCH = $self.get if exists $self.get;

# C<do> is always the same
$self.do = { $self.Proxy };

Even simpler than wrapping!

Or maybe C<get> and C<set> are parts of the Accessor trait, so you need to
apply it to use them.

method bar_attr() is accessor
will get { $.bar_attr }
will set { $.bar_attr = $_ };

method bar_attr(?$val) is accessor {
$.bar_attr = $val if exists $val;
return $.bar_attr;
}

And we can have it both ways:

trait accessor {
[hand-waving declaration for the trait APPLY method] {
$self but= Proxy.new();

if (exists $self.do) {
$self.Proxy.STORE = { $self.do($^a) };
$self.Proxy.FETCH = { $self.do() };
} else {
$self.Proxy.STORE = $self.set // { die "read-only!" };
$self.Proxy.FETCH = $self.get // { die "write-only!" };
}

$self.do = { $self.Proxy };
}
}

Am I off the deep end yet?

> > trait accessor {
> > my $proxy is Proxy;
> >
> > method APPLY( Code $acc : ) {
>
> I don't think that's right.

I'm certain it's wrong. But I had to put something there. It could also
be that the trait is constructed at the same time as the object it applies
to, and its CREATE method gets called with the same arguments. Waiting
for A12...

> > # "Proxy" is a trait/role with FETCH and STORE attributes (?)
> > $proxy.FETCH = { $acc() };
> > $proxy.STORE = { $acc($^a) };
> > # Proxy.for is not set, since I do not know what is being
> > # proxied; I only know how to access it.
> >
> > # add the rw trait
> > $acc but= rw;
>
> C<rw> is a trait, not a role. So you can't C<but> it on. Probably.
>
> > # wrap the applyee
> > $acc.wrap( sub (?$val) {
> > $proxy = $val if exists $val; # so $foo.bar(1) still works
> > return $proxy;
> > } );
> > }
> > }
>
> That looks okay to me, little as I know about traits. I'm a little
> uneasy about declaring the Proxy as a lexical in trait scope; I'd be
> inclined to declare it as a lexical in the C<APPLY> routine, or even
> in the anonymous sub that you're wrapping with.

I had it in the wrapper sub in the first version, but I didn't like that
it was recreating the Proxy object every time someone accessed the
accessor. So I moved it to where I only needed to create it once.

~ John Williams


Larry Wall

unread,
Feb 28, 2004, 12:51:01 PM2/28/04
to perl6-l...@perl.org
On Fri, Feb 27, 2004 at 09:27:20PM -0700, John Williams wrote:

: On Fri, 27 Feb 2004, Luke Palmer wrote:
: > John Williams writes:
: > > I want to get from here
: > >
: > > method bar_attr(?$val) is accessor {
: > > $.bar_attr = $val if exists $val;
: > > return $.bar_attr;
: > > }
: > >
: > I think this is a good idea. Proxies are a little advanced for someone
: > who, say, comes from C# and doesn't understand tying yet. I think
: > you're going about it wrong, though. Here's how I imagine this working:
: >
: > method bar_attr() will access -> $self: ?$val {
: > $.bar_attr = $val if exists $val;
: > return $.bar_attr;
: > }

All the solutions with an optional argument are retro (in the uncool
sense) insofar as it's relying on conditionals where you should be
relying on dispatch.

: Well, I like being able to just say "is accessor" better...

For the default case you don't even need that. You just declare the
attribute:

has $.bar_attr is rw;

and you get a rw accessor automatically. It's only if you want to wrap
some semantics that you have to get fancier.

: > Or, alternatively:


: >
: > method bar_attr() will get { $.bar_attr }
: > will set { $.bar_attr = $_ }
:
: ... but this is good. I'd forgotten about C<do>.

Yes, that is a much clearer and cleaner approach, except to the compiler,
which would still be looking for a C<will do> property there at the
end...

: > And the traits C<access>, C<get>, and C<set> define C<do> to do the


: > right thing. The only trickyness that I see is getting C<get> and
: > C<set> to work together, since they each need to define part of the same
: > proxy class.
:
: It's just setting the FETCH and STORE attributes on a shared Proxy
: class, if I understood that right.
: Maybe the traits put a Proxy property on the method they are applied to,
: and all modify that same Proxy property.

Not good enough, unless the property generates proxies.

: Then, if the get and set traits are just mixed-in with the Method class,


: $self for the trait would refer to the Method itself, so they can just
: define C<do> to return the Method's Proxy property.
:
: # create the Proxy property
: $self but= Proxy.new();
:
: # add the parts defined by traits
: $self.Proxy.STORE = $self.set if exists $self.set;
: $self.Proxy.FETCH = $self.get if exists $self.get;
:
: # C<do> is always the same
: $self.do = { $self.Proxy };
:
: Even simpler than wrapping!

But still wrong...

: Or maybe C<get> and C<set> are parts of the Accessor trait, so you need to


: apply it to use them.
:
: method bar_attr() is accessor
: will get { $.bar_attr }
: will set { $.bar_attr = $_ };
:
: method bar_attr(?$val) is accessor {
: $.bar_attr = $val if exists $val;
: return $.bar_attr;
: }

And that's still wronger...

: And we can have it both ways:


:
: trait accessor {
: [hand-waving declaration for the trait APPLY method] {
: $self but= Proxy.new();
:
: if (exists $self.do) {
: $self.Proxy.STORE = { $self.do($^a) };
: $self.Proxy.FETCH = { $self.do() };
: } else {
: $self.Proxy.STORE = $self.set // { die "read-only!" };
: $self.Proxy.FETCH = $self.get // { die "write-only!" };
: }
:
: $self.do = { $self.Proxy };
: }
: }
:
: Am I off the deep end yet?

Way. :-)

: > > trait accessor {


: > > my $proxy is Proxy;
: > >
: > > method APPLY( Code $acc : ) {
: >
: > I don't think that's right.
:
: I'm certain it's wrong. But I had to put something there. It could also
: be that the trait is constructed at the same time as the object it applies
: to, and its CREATE method gets called with the same arguments. Waiting
: for A12...

Hmm, you've just managed to delay A12 by asking me to explain something
I haven't figured out yet... :-)

: > > # "Proxy" is a trait/role with FETCH and STORE attributes (?)


: > > $proxy.FETCH = { $acc() };
: > > $proxy.STORE = { $acc($^a) };
: > > # Proxy.for is not set, since I do not know what is being
: > > # proxied; I only know how to access it.
: > >
: > > # add the rw trait
: > > $acc but= rw;
: >
: > C<rw> is a trait, not a role. So you can't C<but> it on. Probably.
: >
: > > # wrap the applyee
: > > $acc.wrap( sub (?$val) {
: > > $proxy = $val if exists $val; # so $foo.bar(1) still works
: > > return $proxy;
: > > } );
: > > }
: > > }
: >
: > That looks okay to me, little as I know about traits. I'm a little
: > uneasy about declaring the Proxy as a lexical in trait scope; I'd be
: > inclined to declare it as a lexical in the C<APPLY> routine, or even
: > in the anonymous sub that you're wrapping with.
:
: I had it in the wrapper sub in the first version, but I didn't like that
: it was recreating the Proxy object every time someone accessed the
: accessor. So I moved it to where I only needed to create it once.

And that's the nub of the problem--you can't return the same object
every time, because it has to act like a variable, including being
able to take references to it, temporize it, and such. If it were
just for attributes, you might get by with one static proxy per
attribute per object. However, in the general case, the proxy also
has to function as a closure over the arguments to the method, which
could vary from call to call.

Larry

Aaron Sherman

unread,
Feb 29, 2004, 1:44:47 PM2/29/04
to Perl6 Language List
On Fri, 2004-02-27 at 21:57, Luke Palmer wrote:

> method bar_attr() will get { $.bar_attr }
> will set { $.bar_attr = $_ }

I'm confused by this in only one way... since method bar_attr and the
accessor bar_attr have the same name, how do I write an accessor that
recurses? Does C<.bar_attr> call my method, and not the accessor, or is
there some other way to do that?

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

signature.asc

Larry Wall

unread,
Feb 29, 2004, 5:37:59 PM2/29/04
to Perl6 Language List
On Sun, Feb 29, 2004 at 01:44:47PM -0500, Aaron Sherman wrote:

: On Fri, 2004-02-27 at 21:57, Luke Palmer wrote:
:
: > method bar_attr() will get { $.bar_attr }
: > will set { $.bar_attr = $_ }
:
: I'm confused by this in only one way... since method bar_attr and the
: accessor bar_attr have the same name, how do I write an accessor that
: recurses? Does C<.bar_attr> call my method, and not the accessor, or is
: there some other way to do that?

Within the class you can always get at the attribute via $.bar_attr.
If you call the accessor, it's always virtual, which means it might
be calling out to a derived class's wrapper of the same name. That
method might end up deferring back to your method, of course. Or did
you mean something else by "recurse"?

Larry

0 new messages