stevan / p5-mop-redux

A(nother) MOP for Perl 5
139 stars 36 forks source link

Attribute of static class means the default value for this attribute #158

Open dex4er opened 10 years ago

dex4er commented 10 years ago

I've changed the behavior of generated accessors for static class. I think the best way is to read or change the default value of an attribute.

This is the same behavior as for Python classes:

class A:
    a = 1
    b = 2

class B(A):
    b = 3

print A.a # 1
print A.b # 2
print B.a # 1
print B.b # 3

A.a = 11
B.b = 33

print A.a # 11
print B.a # 11
print B.b # 33

b = B()

print b.a # 11
print b.b # 33

b.a = 111

print b.a # 111
print B.a # 11

The same example coded with mop and this patch:

use v5.14;

use mop;

class A {
    has $!a is rw = 1;
    has $!b is rw = 2;
}

class B extends A {
    has $!b is rw = 3;
}

say A->a; # 1
say A->b; # 2
say B->a; # 1
say B->b; # 3

A->a(11);
B->b(33);

say A->a; # 11
say B->a; # 11
say B->b; # 33

my $b = B->new();

say $b->a; # 11
say $b->b; # 33

$b->a(111);

say $b->a; # 111
say B->a;  # 11
stevan commented 10 years ago

Hmm, I am not sure about this, I generally dislike this kind of dual-purpose behavior. The rw accessor is an instance method, and you are using it as a class method that affects the underlying metaclass.

It is also entirely possible to do this by hand via the mop, so adding a short cut like this is not really necessary.

doy commented 10 years ago

This would also cause trouble with non-trivial defaults (like has $!foo = $_->build_foo or whatever). I also don't like this much.

doy commented 10 years ago

Also, this would be quite easy to do as an extension if you really wanted this behavior - I just don't think it should be the default.

dex4er commented 10 years ago

I did also a proper trait:

use v5.14;

use mop;

use Scalar::Util qw(blessed weaken);

sub static {
    my $attr = shift;
    if ($attr->isa('mop::attribute')) {
        weaken(my $weak_attr = $attr);
        $attr->bind('after:FETCH_DATA' => sub {
            my (undef, $instance, $data) = @_;
            if (not blessed $instance) {
                $$data = $weak_attr->get_default;
            }
        });
        $attr->bind('after:STORE_DATA' => sub {
            my (undef, $instance, $data) = @_;
            if (not blessed $instance) {
                $weak_attr->set_default($$data);
            }
        });
    } elsif ($meta->isa('mop::class')) {
        mop::traits::util::apply_trait(\&static, $_) foreach $meta->attributes;
    }
}
class A is static {
    has $!a is rw = 1;
    has $!b is rw = 2;
}

class B extends A is static {
    has $!b is rw = 3;
}

say A->a; # 1
say B->a; # 1

A->a(11);
say A->a; # 11
say B->a; # 11

B->a(111);
say A->a; # 111
say B->a; # 111

say A->b; # 2
say B->b; # 3

B->b(33);
say A->b; # 2
say B->b; # 33

my $b = B->new;
say $b->a; # 111
say $b->b; # 33

$b->b(333);
say $b->a; # 111
say $b->b; # 333

But still I think this could be the default behavior. Now calling CLASS->accessor causes unspecified effect. It should be more intuitive. I think Python has a really nice sugar for changing the default value of the attribute.