Stevan Little wrote:
Yes, we have. One thing to consider is that it is much easier to get the
"Role order doesn't matter" thing when they are composed. Once you start
keeping the roles around, you run into the possiblity for such things as
"next METHOD" being executed in Role context. I wont even speculate on
what that should/could/would do, because I think we just shouldn't go
there.
We talked briefly about considering this "flattening" as being a
run-time state thing rather than necessarily taking place in the
Meta-objects. Is this still tenable?
Side effects like what happens if you do "higher level" stuff like
run-time modification of Roles after they have been composed into
classes can be addressed seperately.
No, that's correct. We've just basically said that $.x is now a method
call, and as long as you stick to that invocation syntax it just has
to be declared as a method somewhere in the parentage/role-age of
this object. But collisions in $.x declaration will be treated as
collisions in method declaration.
So we really are looking at closure based access to attributes. This
also means that the idea behind P6Opaque as a storage format is very
different. In theory, we could implement these style classes in a very
"Inside-Out" manner. In a way, we could look at:
has $.x is rw;
as being sugar for this (pseudo) Perl 6:
{
my %x;
$?CLASS.meta.add_method('x' => method ($self: ?$value) {
%x{$self.id} = $value if $value.defined;
%x{$self.id};
});
}
Yes, precisely ... except the method returns a Proxy object.
The proxy object responds differently depending on its context.
This is why I wanted the "accessors" to be traits hanging off the
code object itself, as described in http://xrl.us/guyx, as it makes
this not suck - a null accessor can be quickly optimised away to
direct variable access. There is a pugs test for these in
t/oo/attributes/mutators.t
Of course, this interface will need to be extended to support the full
TIE-like interface that proxy objects support. Thankfully as we're a
lot more object oriented these days, I think we might be able to get
away with:
my $hash = %object.hash_property;
:FETCH => should return a Hash object (or Facade), ie .does(Hash)
my $array = %object.array_property;
:FETCH => should return a Array object (or Facade), ie .does(Array)
As everything else is just a method call on the returned object.
Exactly which methods are uppercased into "is accessor"-provided entry
points is then a matter of style; though perhaps for a little sugar
a few should be provided where the function names are awkward to type,
in particular .(post_circumfix:<{}>) and .(post_circumfix:<[]>)
To summarise with code for those who are lost;
has $.foo;
would be sugar for:
has $foo;
method foo is accessor( :FETCH { $foo },
:STORE -> $x { $foo = $x },
);
If you use \( $object.foo ) - that is, create a reference to the .foo
property, you get a Proxy object; the above should be considered
functionally equivalent to;
has $foo;
method foo is rw {
return Proxy.new(:FETCH { $foo },
:STORE -> $x { $foo = $x },
);
}
This is extended into the other sigil types;
has %.foo;
is sugar for this:
has Hash $foo; # or has %foo, but really, the point is it's
# an implementation detail, right?
method foo is rw {
return Proxy.new( :FETCH{ $foo }, # or a facade
:STORE -> Hash $x { $foo = $x },
)
but {
method post_circumfix:<{}>($key) is rw {
return Proxy.new( :FETCH{ $foo{$key} },
:STORE -> $x { $foo{$key} = $x },
);
}
};
}
Doesn't that look nice and ugly! :-D
Note that this means that taking a reference to a hash slot is actually
returning you a proxy object that will access the hash when you call it.
Sneaky, huh? This means that this doesn't have to auto-vivify the
slot any more, closing the caveat found in
t/builtins/arrays_and_hashes/exists.t by Darren Duncan:
my $slot_ref = \( %foo<bar> );
This looks much tidier with Luke's nomenclature of "is accessor";
has Hash $foo; # or has %foo, but really, whatever
method foo is accessor
:FETCH { $foo },
:STORE -> Hash $x { $foo = $x },
:HASHENT -> $key is accessor
:FETCH { $foo{$key} },
:STORE -> $x { $foo{$key} = $x };
Add brackets to suit. The HASHENT is an early idea for an optional
shortcut; to avoid creating a facade when "all you wanna do" is define
what happens when you treat the object property as a Hash/Array via
$object.property{key}. But it's still sugar for the above "but { }"
clause (or any other way of returning a Facade object that .does(Hash)).
Well, if we ditch the P6Opague type (as we currently know it) and use
the Inside-Out method I show above. Then we just don't actually have
class/instance attributes anymore at all. We really just have methods,
some of which happen to be closures around per-instance values. Of
course this means there is no direct access to attributes at all (which
I think it not a bad thing, but I am into B&D like that).
So, if you want a "visitor pattern", you grab a visitor iterator
via the Meta-Objects, right? Which could also solve the
STORABLE_freeze/STORABLE_thaw / Pixie::Complicity / etc problem by
unifying the way that "foreign" objects can marshall themselves to Perl
objects when required.
*sniff* *sniff* this is smelling better and better :)
Sorry, I'll put that out.
Sam.