develooper Front page | perl.perl6.language | Postings from February 2004

Thinking about Accessors

Thread Next
From:
John Williams
Date:
February 27, 2004 18:38
Subject:
Thinking about Accessors
Message ID:
Pine.LNX.4.33.0402271931010.24668-100000@sharkey.morinda.com
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



Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About