Beefy Boxes and Bandwidth Generously Provided by pair Networks Cowboy Neal with Hat
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Some thoughts on Moose Attributes

by John M. Dlugosz (Monsignor)
on May 01, 2011 at 13:22 UTC ( #902336=perlquestion: print w/ replies, xml ) Need Help??
John M. Dlugosz has asked for the wisdom of the Perl Monks concerning the following question:

In my first real code using Moose, I noticed some things about attributes.

Basically, it is not nice to have to call functions to get and to set each time. I noticed later that some use of "native traits" delegation can mitigate that, but not completely, as it would lead to a proliferation of specific methods; but that's an issue really because Perl built-in types like arrays are not themselves accessed via method syntax and built-in functions don't take references.

The real issue, that can't be put off on other things still needing to catch up, concern very simple types like plain numbers and strings.

Consider:

my $linenum= $self->input_line_number; ++$linenum; $self->input_line_number ($linenum);
Or perhaps$self->input_line_number(1+$self->input_line_number);. But, recall that the ++ syntax is beloved by millions and for a reason! It is quite a bit out of the way to not be able to manipulate things in-place.

Now in Perl6, the accessors return lvalues, so something like ++$self->input_line_number would work just fine.

So here is a specific question: why not have an lvalue accessor in Moose? That could be enabled on request, or the default when it is declared to be a non-reference type.

More generally, how are people coping with the large affordance distance between the method's code and the per-class instance data? It's always been large in Perl 5, but at least I could refer to the slots (via hash access) as lvalues. It seems to be a chore, all the time, for code to get to its values. In C++ you just say x in scope; in Moose you have to say $self->x twice, to get and again to set, and work on a copy.

Comment on Some thoughts on Moose Attributes
Select or Download Code
Re: Some thoughts on Moose Attributes
by BrowserUk (Pope) on May 01, 2011 at 14:56 UTC
    Or perhaps $self->input_line_number(1+$self->input_line_number);. But, recall that the ++ syntax is beloved by millions and for a reason! It is quite a bit out of the way to not be able to manipulate things in-place.

    I've made that same observation here several times, and it is one of the reasons that underlies my earlier response on a similar topic. In particular, the last couple of paragraphs.

    The oft noted problem with lvalue setters is that, as implemented in Perl 5, you cannot validate the value assigned. But, this is only really a problem if you have public setters for attributes, where you do not have control over the code that will do the assignment.

    As previously discussed, I don't allow public accessors. Which means that any values that get assigned to attributes come into the object as (or are derived from) the arguments to public methods where I have the opportunity to validate them before setting them into the attributes. This means I do not have to waste cycles by having the setters validate the values every time they are set.

    External values are only validated as they transition the public/private boundaries. Internal code that modifies attributes is under my control, and can be validated by testing during development thereby avoiding the overhead of continual revalidation during production runtime.

    Of course, once you do away with the need for runtime validation of individual attributes, then the need(*) for internal use accessors disappears also, which leaves you directly accessing the attributes internally and doing away with another layer of subroutine call and so benefiting performance even further.

    (*)Of course, some will argue that you should use accessors rather than direct access internally anyway. Because, they will suggest, this allows you to isolate the internal code from future changes in the structure of the attributes.

    I believe this to be a justifiction. Here's why. Let's say you have a piece of code in a method that does:

    class Xyz; attribute x; attribute size; someMethod( arg, ... ) { ... assert( arg > x && arg < y ); ... x = ( x * arg ) % size; ... }

    With internal accessors you might, at not inconsiderable cost, write that as:

    class Xyz; attribute x; attribute size; someMethod( arg, ... ) { ... assert( arg > x && arg < y ); ... self->set_x( ( self->get_x() * arg ) % self->get_size() ); ... }

    But the question is, what does that buy you that is worth the cost? In what way could x change in the future such that the change to x would not require the code that uses x to be modified also?

    • If it changed name (to say y): are you going to leave the setter and getter names untouched?
    • If it changed type;
      • say int became float: In perl, generally, no change is required. Except that % mod doesn't play well with float types (it integer-ises), so you'd have to change the code anyway.

        In (most) other languages, the math would also still work. And for those where it wouldn't--for example languages that use different operators for int and float--then again, you'd have to modify the code also.

      • Say int became a string:

        In perl, same thing. Perl would take care of the conversions.

        In other languages, you wouldn't perform math on a string. So either the type wouldn't change, or the code would have to.

    I've still yet to see a good example of a case where using internal accessors. allows the nature of the attribute to change, without also requiring change to the code that uses them?


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      If I understand you, the (only) reason against having lvalue accessors is because it can't run the validation, coersion, triggers, etc. on a SET.

      Meanwhile, you say none of that is necessary on private data anyway.

      But, the representation of the class is opaque. I can't get at the slot without going through the MOP or inline functions generated by the MOP.

      Does the average class need to have complete freedom to separate instantiation representation from the class? That's akin to the 'tie' feature in classic Perl 5, and agree that this is better served using abstract interfaces. I won't be changing the "bits" suddenly to a different implementation. Hmm, or will I? In Perl 5.xx+ maybe an efficient XS-based slot mechanism will be added to some platforms, and my existing classes and most of CPAN and all the Catalyst stuff should just work and suddenly work faster! So, maybe I want to keep my options open just now.

      Now roles on the other hand can be added to any kind of class, and can't assume the storage layer in use by whatever class it winds up in. So it should be more abstract, unless it's app-specific and knows something about where it's going.

      So, maybe you and I would both like a lvalue option. Declare my private instance data with some keyword or something that makes it generate an lvalue accessor for combined get/set usage, with no init-arg, type check, etc.

      Another idea would be a Moose implementation layer that guaranteed a certain naming convention among its use of hash keys, and was known to be a blessed hash. Then you could add your own instance data among the $self->{keys} in the classic manner. You would define that you wanted that implementation metaclass (whatever it's properly called in Moose) when you created that class.

        In Perl 5.xx+ maybe an efficient XS-based slot mechanism will be added to some platforms, and my existing classes and most of CPAN and all the Catalyst stuff should just work and suddenly work faster! So, maybe I want to keep my options open just now.

        Hm. That seems like a prime example of the whatifitis I've been decrying elsewhere :)

        Without claiming any great expertise in XS, the idea that calling an XS subroutine to access a variable could ever be quicker than a direct hash element look up doesn't seem at all feasible, let alone likely.

        I guess using internal accessors would (in Perl) facilitate changing the blessed reference type from hashref to arrayref. Or possibly vice versa, though a reason for doing the latter doesn't instantly spring to mind. But the main reason for switching from a hashref to an arrayref is performance. Array lookups using constant subs is a tad quicker than hash lookups.

        But if performance is the reason for the change, then you'd achieve greater gains by dropping the accessor methods and going direct to the hash.

        The only other reason I can think of for why you might consider the switch from hashrefs to arrayrefs is to reduce storage requirements. But if you have a class that has sufficient instance attributes to make that switch significant on a per-instance basis, then there is something cock-eyed with your design anyway. And if you are using sufficient individual instances of a given class to make the combined storage requirements of the instances worth switching, then you should be using an aggregate rather than individual instances anyway.

        So, maybe you and I would both like a lvalue option.

        The only half-good reason (that I can see) for using lavlue accessors for private attributes is the simplified syntax. $self->x++; is nicer than $self->{x}++; or $self->[X]++;.

        And for non-performance critical classes that would, indeed has been, sufficient reason for me to use lvalue methods.

        Now roles on the other hand can be added to any kind of class, and can't assume the storage layer in use by whatever class it winds up in. So it should be more abstract, unless it's app-specific and knows something about where it's going.

        My thought processes come a little unstuck when it comes to Roles. Basically, I haven't used them.

        The main disadvantage of composition relative to inheritance is that where the latter allows $self->superclassMethod();, the former requires $self->{compositeObject}->method();.

        I mostly see Roles as a way of avoiding inheritance whilst avoiding the double indirection required by composition. In that scenario, the internals of the Roles class are (by my rules) entirely transparent to and inaccessible from the incorporating class. That is, there doesn't seem much point in incorporating a Role into a class, if the class needs to manipulate the attributes within the Role directly, The class might just as well declare an instance and manipulate it. The Role needs to provide methods that do things to or with the state they incorporate, otherwise there is little purpose in them.

        And that brings me to the thorny issue of the Role operating not on its own internal state, but rather on the attributes of the class into which it is incorporated. This, on the surface at least, appears to be a useful possibility.

        And as far as I can see, there are 3 ways a Role could be written to have access to its peers attributes:

        • It could require that its peer use well-known names for any state and/or methods that it needs to access.

          Whilst simple to implement, it seems that this would be a particularly crude way to go about it. Whatever names were chosen might make sense in terms of the generic Role, but no sense at all in terms of the classes that use that Role. It is easy to see that a method provided by a Role might have a positive connotation in one calling class but a negative connotation in another. And if the Role specifies a neutral 'well-known name' then you end up with the worst of all worlds where it makes no sense in the contexts of either type of using class.

        • It might be possible to it by introspection.

          Though it would still need to know what methods to look for, so I see no advantage in that.

        • Or the using class could provide either names or references (delegates) to the Role's constructor.

          This seems to be the 'best' method (I can think of), but do Roles have constructors?

        As you can probably tell, I'm not really up with how Roles are actually implemented. Or used for that matter. I did start to look into their implementation in Moose once a while ago, but the complexity left me cold. Not so much that I couldn't follow it through all the layers--though it was definitely tough going in several places. Mostly, I just got disheartened to see just how much complexity was involved to achieve something that I can implement myself using standard Perl in a couple of dozen lines.

        As you've probably worked out by now, I'm not, nor ever likely to be, a Moose user. But I'm not critical of Moose. If you need its facilities, or can llve with its costs for your application, then it is an absolutely amazing piece of work with some major benefits, especially for Perl/OO beginners.

        I just don't happen to fit into any of those categories.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
        Does the average class need to have complete freedom to separate instantiation representation from the class?

        As an implementor, I answer wholeheartedly yes.

        The naïve implementation of an object system in Perl 5 (stolen almost whole cloth from Python) actively presentsprevents many simple and nearly all clever optimizations. Decoupling representation from behavior at the class or metaclass layer would allow gradual and progressive optimizations for memory usage or compatibility with C data structures or persistence mechanisms or JIT with much less work (and much, much less cleverness) than doing so with the simple hash-based lookup mechanism currently in place in Perl 5.

        In the Moose API, compatibility with existing classes is probably more of a concern.

Re: Some thoughts on Moose Attributes
by stvn (Monsignor) on May 02, 2011 at 01:43 UTC
    Basically, it is not nice to have to call functions to get and to set each time.

    Perhaps, but this is the cost of encapsulation.

    I noticed later that some use of "native traits" delegation can mitigate that, but not completely, as it would lead to a proliferation of specific methods;

    Actually the best usage of the native traits feature does not expose all the features of the underlying data structure, but instead uses the delegation methods to craft an appropriate API that doesn't at all expose the underlying data structure. This way if you decide later on down the road that it is better to use Set::Object then a plain array, you only need to change a few internal methods and none of your classes consumers need to care.

    If really what you want to do is expose all the delegate-able methods of the data structure, I recommend looking at something like Moose::Autobox. Though to be honest, I really dislike Autobox for a number of reasons.

    but that's an issue really because Perl built-in types like arrays are not themselves accessed via method syntax and built-in functions don't take references.

    Again, if that is what you want, Moose::Autobox, but if you want the encapsulation/data-hiding/implementation-hiding that is one of the key concepts of OOP, then you want to use your native delegations carefully and focus on exposing the correct API, not the underlying data structure you implemented it with.

    The real issue, that can't be put off on other things still needing to catch up, concern very simple types like plain numbers and strings.
    ....snip...
    Or perhaps$self->input_line_number(1+$self->input_line_number);. But, recall that the ++ syntax is beloved by millions and for a reason! It is quite a bit out of the way to not be able to manipulate things in-place.

    Why not use the Moose::Meta::Attribute::Native::Trait::Counter native attribute? With an attribute like this

    has 'input_line_number' => ( traits => [ 'Counter' ], is => 'ro', isa => 'Int', default => 0, handles => { 'inc_line_number' => 'inc', 'dec_line_number' => 'dec', '_reset_line_number' => 'reset', 'skip_ahead_two_lines' => [ 'inc' => 2 ] } );
    You would have single method calls to increment and decrement your line number, which underneath are inlined subroutines which do direct access to the underlying hash (avoiding that 2 method call overhead). Also, as you can see I added a private reset method as well, and another method which uses the native attribute "currying" feature to create a method that will increment by 2 every time.

    Remember, this is Perl, there is always more than one way to do it, and Moose tries very very hard to be as Perl-ish as possible.

    So here is a specific question: why not have an lvalue accessor in Moose?

    Because lvalue subs are poorly implemented in Perl, they do not in any way allow for hooks to be added in and they essentially expose the underlying data object as well as its type, which pretty much breaks encapsulation (if I know I can ++ on the lvalue sub, then I know that the underlying value *must* be a number, if I want to change that implementation detail, too bad, my class consumers will break). Sure you can work around this with some awful tie() hacks and perhaps even using Variable::Magic and I am pretty sure people have actually done this, however the code was obviously too scary/ugly to release as an actual module.

    Now, don't get me wrong, I actually love the idea of lvalues. When I code in C# (which I have to do every once in a while), I really enjoy a number of the language features it provides, one of which is Properties, which is (IMO anyway) lvalues done right.

    It's always been large in Perl 5, but at least I could refer to the slots (via hash access) as lvalues. It seems to be a chore, all the time, for code to get to its values. In C++ you just say x in scope; in Moose you have to say $self->x twice, to get and again to set, and work on a copy.

    I know, wouldn't it be awesome if we had proper classes? Which had proper scoping (per-instance, not just block-scoping)? I would absolutely love to see that come about some day, and on that day I will almost certainly stop using Moose and start feverishly porting over any and all useful MooseX:: modules to work with the new and beautiful world of real, proper classes in Perl (notice i didn't say OO, but classes, there is a big difference).

    But until then we are still stuck in this ghetto of Perl back-compat and DIY-OOP-1990-isms. You see, this is exactly why I wrote Moose in the first place. I had spent over a year working on the Pugs project, prototyping MOP after MOP and tasting the awesomeness that was to be Perl 6 OO, only to have to go back to vanilla Perl 5 OO for $work code. Eventually I decided I needed a way to do the great stuff I could do in Perl 6, in Perl 5, and I needed it to be stable and sane enough in the Perl 5 world that I could use it in production.

    Moose is not the ideal OO system, in fact, it is very very far from it. A truly great OO system would not have to exist as a library atop the language itself, it would not have to deal with some of the really fugly crap that we have to deal with in order to make Moose work and just as importantly, make Moose play well with non-Moose code (after all, what is Perl without the CPAN, dead I tell you, dead! (just kidding, but you get the idea)).

    In the past I have referred to Moose as a disruptive technology and underlying this reference is my hope that someday, like any good disruptive technology that gains enough momentum, it will get replaced by an even better version of itself. Hopefully Moose will help serve as a stepping stone to the next level, be that Perl 6 or just a really far off version of Perl 5, pretty much as long as it is Perl I will be happy.

    Anyway, enough preaching for a sunday, time to go watch some TV with the wife and sip a nice beer.

    -stvn
      Because lvalue subs are poorly implemented in Perl, they do not in any way allow for hooks to be added in and they essentially expose the underlying data object as well as its type, which pretty much breaks encapsulation (if I know I can ++ on the lvalue sub, then I know that the underlying value *must* be a number, if I want to change that implementation detail, too bad, my class consumers will break).
      In C++, if I refer to a field, I can't readily make that get/set or in-place modification do something else. It doesn't have "properties" ala C# and other Microsoft languages. Accessing a data member is syntactically distinct from calling a function in every way, since functions must have parens even if there are no parameters.

      So, I'm used to that. I refer to data members directly only when they are private implementation detail, and use accessors for public API.

      It is mitigated somewhat because names are checked at compile time, so if I had to upgrade a bare field to something hiding behind an accessor, I could remove the field and be sure that all uses were noticed before I ran anything.

      Basically you make the same point as browserUK. The reason lvalue accessors are not used is because it doesn't hook setting to perform type checks, coersions, and whatever other hooks.

      You covered a lot of ground in your post, so I'll reply to different parts separately or perhaps even start new threads.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://902336]
Approved by Corion
Front-paged by Eliya
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (6)
As of 2014-04-20 04:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (485 votes), past polls