stevan / p5-mop-redux

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

different behaviour for attribute traits for mop and perl 6 #126

Open forwardever opened 10 years ago

forwardever commented 10 years ago

mop

use mop;
use v5.16;

class Test {
    has $!one is ro = 1;
    has $!two = 2;

    method sum {
        $!one + $!two
    }
}

say my $test = Test->new;
say $test->sum;
say $test->one;

Output

Test=SCALAR(0x26e7748)
3
1

Perl 6

class Test {
    has $!one is readonly = 1;
    has $!two = 2;

    method sum {
        $!one + $!two
    }
}

say my $test = Test.new;
say $test.sum;
say $test.one;

Output

Test.new()
3
one(Test.new())

(not sure what the last line of the output means though, maybe due to my older version of rakudo?)

Perl 6

now with $.one

class Test {
    has $.one is readonly = 1;
    has $!two = 2;

    method sum {
        $!one + $!two
    }
}

say my $test = Test.new;
say $test.sum;
say $test.one;

Output

Test.new(one => 1)
3
1
forwardever commented 10 years ago

so actually the comment in https://github.com/stevan/p5-mop-redux/issues/125 makes sense, as the author is using the ro trait

"This is almost a complete copy of Perl 6 syntax. At the moment only attributes with the $! twigil is supported, and they behave as public attributes (even though in Perl 6 they indicate private attributes)."

stevan commented 10 years ago

The behavior of the second example makes no sense at all, I think perhaps you have an old version of rakudo

stevan commented 10 years ago
12:55 stevan_: hey 6ixers
12:55 stevan_: can anyone explain what is going on in the second example here: https://github.com/stevan/p5-mop-redux/issues/126
12:56 arnsholt: There won't be an accessor if you declare your attribute has $!foo
12:57 arnsholt: So it looks like there's a method called one further up in the hierarchy
12:57 FROGGS: and it treats "one" as a junction
12:57 FROGGS: ahh, or that
12:58 stevan_: wtf
12:58 moritz: r: say Any.one
12:58 camelia: rakudo 2ce544: OUTPUT«one()␤»
12:58 stevan_: so it is impossible to generate an accessor for a private attribute?
12:58 moritz: yes, there are methods in Any for the junctions (any, all, none, one)
12:58 stevan_: or is that just not what the readonly trait does?
12:59 moritz: stevan_: if it has an accessor, it's not private anymore
12:59 timotimo: readonly doesn't create an accessor.
12:59 moritz: read-only in the default
12:59 moritz: if you want an rw-accessor, write  'has $.two is rw;'
12:59 stevan_: can you do "has $!two is rw"?
12:59 moritz: stevan_: doesn't make any sense
13:00 moritz: r: class A { has $!two is rw }
13:00 timotimo: the ! means: "no accessor autogenerated"
13:00 camelia: rakudo 2ce544: ( no output )
13:00 stevan_: ok, let me rephrase
13:00 moritz: r: class A { has $!two is rw }; say A.new.two
13:00 camelia: rakudo 2ce544: OUTPUT«No such method 'two' for invocant of type 'A'␤  in block  at /tmp/1ZHVwC0PL6:1␤␤»
13:00 moritz: ok, it just ignores the 'is rw'
13:00 moritz: I guess it could also carp at compile time
13:00 timotimo: if it should, i think i can implement that :)
13:00 moritz: timotimo: +1
13:01 stevan_: ok, anyway
13:03 stevan_: I guess I am confused about what is rw and is readonly actually do
13:03 stevan_: I assumed they created accessors to the attribute regardless of whether it was private or public
13:03 stevan_: like C# properites
13:03 timotimo: yes, that assumption is in fact wrong
13:04 stevan_: timotimo: can you clear things up for me then?
13:04 timotimo: sure
13:04 stevan_: thanks :)
13:04 timotimo: if you write has $.foo, you'll get an accessor generated for you
13:04 timotimo: if you write nothing more, that's a readonly accessor
13:04 stevan_: a lvalue style accessor right?
13:04 timotimo: yes
13:04 stevan_: k
13:04 timotimo: if you write is rw after the $.foo or after the class, you'll get a read-write accessor (lvalue as well)
13:04 stevan_: k
13:05 stevan_: and is readonly is really just redundant then
13:05 stevan_: since default is readonly
13:05 timotimo: yes, that's true
13:05 stevan_: so then, that explains why this doesnt work with private attributes
13:05 timotimo: except, i suppose, if your class is "is rw", you can have single attributes "is readonly"
13:05 stevan_: right
13:05 stevan_: that makes sense
13:06 timotimo: yes, and the .one thing is just a surprise that was derived from Any
13:06 stevan_: so, the reason this doesnt work with private attributes is because you are not really generating an accessor method so much as allowing field access
13:07 stevan_ assumes that some kind of optimizer might remove any method call overhead or something
13:07 timotimo: i'm not sure what you mean by that. what does "this" refer to in the first sentence?
13:07 stevan_: if it is even there at all
13:07 timotimo: there is method call overhead, yes
13:07 stevan_: this == "has $!foo is rw"
stevan commented 10 years ago

It seems we are not 100% following Perl 6, but that is because our traits are just a lot simpler then theirs.

stevan commented 10 years ago
13:29 jnthn: stevan_: Just to make sure all is clear: the "is readonly" and "is rw" traits on an Attribute meta-object simply set whether the accessor will be rw or not. It's the . vs ! is what controls a has_accessor property on the Attribute meta-object. At class composition time (closing curly) we compose all the attributes, at which point an accessor method is added to the method table of the class.
13:29 jnthn: stevan_: And the rw property on Attribute - settable through the trait - controls the kind of accessor that is genreated.
13:29 japhb__: ... assuming there wasn't a method of that name already.
13:30 jnthn: stevan_: If you don't generate an accessor then those traits still set things on Attribute. It's just ultimately useless to have done so... :)
13:30 jnthn: japhb__: Correct, modulo that.
13:30 jnthn: And also there's:
13:30 jnthn: r: class A { has $.x; has @.x; }
13:30 camelia: rakudo 2ce544: OUTPUT«===SORRY!===␤Two or more attributes declared that both want an accessor method 'x'␤»
forwardever commented 10 years ago

I think it is a strategic decision you have to make: if you want to get the mop in core, and on the other hand, the perl 6 guys will get perl 5 running on their vms (not sure if we will ever get there), then these kind of inconsistencies might be a problem

so are there any reasons not to introduce a $. (dollar dot) twigil to autogenerate accessors ?

stevan commented 10 years ago

@forwardever - the $.foo is really not possible since in order to keep compat with Perl 6 we would need to introduce lvalue accessors and they suck in Perl 5.

I think the solution here is maybe to just document the differences more thoroughly. The reality of the situation is that we are generating normal method accessors, whereas Perl 6 is actually just enabling certain behaviors on already existing specialized lvalue accessors (setting the r/w switch for instance). So already we are pretty far away from where they are.

And all this said, it is entirely possible for me to write a rw trait for Perl 6 that does what ours does, although that would be stupid.

I agree, this is a tricky one, but I am pretty sure that switching to $.foo is not the right answer as it gives us even more issues to worry about.

Now, what this does raise is the interesting possibility of using our own twigil and not using $!foo, but instead using something possibly more pleasing to the eye such as $:foo.

forwardever commented 10 years ago

1.) my first choice would be to add lvalue accessors

2.) as you think this is not possible, my second choice would be to create just simple accessors $self->bar and $self->bar('foo') using $. twigil

3.) if that is also a problem, I would prefer to remove attribute traits (just ro and rw) completely and keep open the possibility to add lvalue accessors or simple accessors in a future version of mop

accessors would have to be created manually, as it is the case in Perl 5 now, or people could add their own traits (as the one that is used currently in the mop)

4.) I totally dislike $:foo (it might be a dead end), as you can see from my examples above, it is trivial to currently switch between mop and perl 6, which is rather great

stevan commented 10 years ago

1 - is just not going to happen, they require too much overhead in Perl 5 and have too many edge cases.

2 - would be even more confusing for Perl 6 <-> Perl 5 compat since I am not willing to support the default readonly accessor

3 - I think that if we do not include rw and ro in the core, then people will really be annoyed, i see them as a must-have

4 - well, it doesn't have to be : it could be something else, the point being is that if we deviate from Perl 6, we should really deviate (though i am not sure I agree with this myself)

forwardever commented 10 years ago

okay, problem is that after years of effort to find some kind of standard for OO in perl 5 (and these days, there are standards for everything in programming), I really have doubts that inventing even another language (next to Perl 6, Moose/Mouse/Moo, classic old school OO in perl 5 ...) makes sense

these days, running a complex software in perl often involves 5 or 6 OO systems at the same time

if I got your dead-end presentation right, perl 6 might be the long term future (because the way perl 5 is implemented), so it is my strong believe that perl 6 compatible syntax is the way to go, even if I don't like every single aspect of it (e.g. instance vars not available in super clases, as discussed some weeks ago)

just wanted to provide some reasoning for my choices above, so that you better understand why I would prefer perl 6 like syntax

regarding 2.) : you wrote "I am not willing to support the default readonly accessor" right now, you have to write "is ro" and "is rw" as well, so where is the difference to the current solution?

stevan commented 10 years ago

RE: 2) if we want with $.foo then in order to maintain compat with Perl 6 we would need to have a default read-only accessor, currently we have no default accessors.

I completely agree that large school Perl 5 development is a pain, and that all the various OO systems is part of that problem. This is clearly because we didn't have a sufficient standard way to do it, and that is what I am trying to solve. Maybe it will alleviate the problem, maybe it will make things worse, only time will tell.

As for my feeling that Perl 6 is the long term future, yes, I think it is the long term future for Perl, simply because Perl == Larry and Larry is working on Perl 6. The rest of us are simply trying to improve the situation he left behind in Perl 5 since Perl 6 is not ready yet.

Now, this all said, what we are really talking about is some degree of syntax level compatibility, of which we are already pretty far off. For instance, method calls are still done with ->, we do not have types, not everything is a reference (ex: in Perl 6 @foo is an object reference, in Perl 5 it is not). We are also missing a lot of other features, too numerous to mention, and lastly our MOP APIs are very different (which makes sense because they are APIs to two very different underlying systems).

While I agree, the case you show is kind of annoying and tricky, it is easily explained in the documentation. The key thing is that the philosophies underlying what we are doing and what the Perl 6 traits system are doing are very similar. But the the meta-attribute objects that the traits are being applied to are very different and so therefore they will support different behaviors.

forwardever commented 10 years ago

not sure whether I understand the problem regarding 2.)

you wrote "currently we have no default accessors", so if you say "is ro", isn't there a default accessor created?

maybe, I completely misunderstand

thanks for your comment on the other part of my message, but still think you (we) should keep it close to Larry :)

stevan commented 10 years ago

re: 2) you suggested

... to create just simple accessors $self->bar and $self->bar('foo') using $. twigil

If we use the $.foo twigil and want to keep in line with Perl 6, we need to automatically generate a read-only accessor. I am against any kind of "default" accessor generation, that is a decision to be made by the programmer, not the framework.

And I agree we should keep close to Larry on this, but I think we are doing that in the way we implemented traits. Really what differs is the underlying attribute meta-object we are operating on. In our case, we only have a private attribute ($!foo) and so the only way we can make it accessible is to generate a method. In the case of Perl 6, the $.foo attribute meta-object already includes a specialized lvalue read-only accessor and applying the rw trait to it simply changes the behavior of that accessor. So you can see that our attribute meta-objects support different capabilities and the rw traits do different things, but ultimately the trait system (subs currently in scope) and the spirit (providing access to an attribute via outside accessible methods) are the same.

forwardever commented 10 years ago

maybe it is too late to understand everything, but just for clarity

right now

# private attr
has $!foo;

# generate public accessors
has $!foo is ro;
has $!foo is rw;

my suggestion

# private attr
has $!foo;

# generate public accessors ($instance->foo and/or $instance->foo('bar')
has $.foo is ro;
has $.foo is rw;
# or just (readonly by default??)
has $.foo;

so by using $. it is the developer who decides if accessors are created

stevan commented 10 years ago

No, I understand what you are suggesting, but that differs from Perl 6 more IMO, specifically because we are not willing to support the default readonly for has $.foo

forwardever commented 10 years ago

1.) at least when it comes to the api (not the implementation), it seems very close to perl 6, so question is: why does it differ more (suppose you mean implementation, as described above?)

2.) why not default "ro"?

I mean the situation right now is: has $!foo; == default no accessor at all

stevan commented 10 years ago

1) I think that both of them are equally different from Perl 6, but I think that the lack of an expected feature (no default ro accessor) would be more surprising then the ability to add an accessor somewhere you can't in Perl 6.

2) the interface of a class is sacred, it should be completely under the control of the program author, anything automatic goes against this. It would also require us to have something like this: has $.foo is without_accessor for those cases when you don't want an accessor.

forwardever commented 10 years ago

okay, maybe my understanding of perl 6 or mop is too limited, so last question for today: why whould you use

has $.foo is without_accessor

when you can just use

has $!foo;