Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

OO-style modifiers for 'sub' ?

by Gilimanjaro (Hermit)
on Jan 24, 2003 at 17:10 UTC ( [id://229640]=perlmeditation: print w/replies, xml ) Need Help??

This is my first meditation (well online meditation anyway), so please forgive me if it isn't up to the usual Monk standards, or if it turns out to be total non-sense... (I am after all only a simple scribe)

More and more I am building my Perl applications in a nice an clean OO way, with constructors, inheritance, prototypes, you name it. Lately I'm finding myself repeating the same few lines of code in every single instance method;

sub foo { my $self = shift; croak "I am an instance method" unless $self and ref $self and $self->isa(__PACKAGE__); # actual body of code }
And the same with 'ref' negated for each class method. This has become my generic valid invocation checker... I know 'ref' should probably be 'blessed', but my Perl knowlegde may be a bit out of date. For class methods, $self should in fact probably be checked with /\W/, and possibly another check to see if the namespace is defined.

I was wondering if it might make sence to add 'modifiers' to the 'sub' keyword that would indicate to the interpreter to do checks like this... Something like:

method sub {} # $SELF=shift, check $SELF & isa instance method sub {} # =method modifier + ref $SELF class method sub {} # =method modifier + !ref $SELF & /\W/ & de +fined namespace

The 'method' modifier would setup $SELF automagically for instance. This would 'standardize' the syntax used in method code, while still allowing 'old-fashioned' code.

Maybe I've been working in Java too much, but this kind of run-time checking would greatly enhance my coding experience I think...

Naturally you guys will tell me that Perl 6 has all these things, and I will be shamed for not knowing enough about Perl 6... :)

Replies are listed 'Best First'.
Re: OO-style modifiers for 'sub' ?
by Aristotle (Chancellor) on Jan 24, 2003 at 17:17 UTC
    Without having spent much thought on it, I'd say you can probably cobble something like that together with Attribute::Handlers and one of the many sub wrapping modules such as Hook::WrapSub, though I'm not sure the idea is good in the first place. I may come back to post some code and/or further arguments after dinner. :)

    Makeshifts last the longest.

      You've just pointed me to two modules (or even families of modules) I knew nothing about... They seem incredibly powerfull and can definitely achieve the sort of thing I was looking for...

      You well and truly deserve your dinner!

Re: OO-style modifiers for 'sub' ?
by dpuu (Chaplain) on Jan 24, 2003 at 20:58 UTC
    As other people have pointed out, checking that you are a specific type is just not-Perl. In fact, its not OO either, because it disables inheritance.

    C++/Java approach would be to define base classes (as interfaces) and inherit from them. The you can say

    sub foo { my ($self) = @_; croak "..." unless ref($self) && $self->isa("AnInterface") }
    But this would have problems: First, you may need to check multiple interfaces; second you have to include them in your ISA list; third, its all getting very verbose, and not-Perl.

    So lets turn it around a bit:

    sub foo # requires interfaces InterfaceA, InterfaceB { my ($self) = @_; InterfaceA::validate($self); InterfaceB::validate($self); }
    This client code is much simpler: you are passing the implementation of the check down to a module that knows what it means to be that module. An you no longer require explicit inheritance:
    sub InterfaceA::validate { my ($candidate) = @_; error unless ref($candidate); error unless $candidate->isa("InterfaceA"); # explicit ISA } sub InterfaceB::validate { my ($candidate) = @_; error unless ref($candidate); error unless $candidate->can("fn1"); # implicit ISA error unless $candidate->can("fn2"); }
    Ignoring the issue of naming, it is clear that having an interface know what methods are needed is more powerful than requiring explicit inheritance. If an object supports all the methods in an interface, then it can be said to implement that interface.

    One last thing: the names I've chosen are non-optimal, but if validation is common then you'd think that you want concise names. But you can work around this. We might name our validation methods "is_implemnted_by", and create a wrapper function (in a shared utilities module):

    sub validate_object { my ($self, @interfaces) = @_; error unless ref($self); foreach my $interface (@interfaces) { my $fn = $interface . "::is_implemnted_by"; error unless $fn->($self); } } #... sub foo { my ($self) = @_; validate_object($self, qw/InterfaceA InterfaceB InterfaceC/); } sub bar { my ($self) = @_; validate_object($self, map {"Interface$_"} qw/A B D/); }
    --Dave
      sub foo # requires interfaces IntA, IntB { my ($self) = @_; IntA::validate($self); IntB::validate($self); }
      Excellent! I am impressed - a very simple, concise and - as far as I can tell - robustly versatile approach. Seems to be the perfect distribution of responsibilities. It certainly deserves attention - if it doesn't get much here, please repost it as a root node. I'm very curious to hear the gurus with theoretical background poke at this and comment on it.

      Makeshifts last the longest.

      As other people have pointed out, checking that you are a specific type is just not-Perl. In fact, its not OO either, because it disables inheritance.

      All the tests that have been proposed so far work with inheritance. The inheritance problem is when the subroutine isn't called as a method.

      C++/Java approach would be to define base classes (as interfaces) and inherit from them. The you can say

      sub foo { my ($self) = @_; croak "..." unless ref($self) && $self->isa("AnInterface") }

      But this would have problems: First, you may need to check multiple interfaces; second you have to include them in your ISA list; third, its all getting very verbose, and not-Perl.

      If you had a base class (or interface) it would be part of the class hierarchy. So you would have:

      use base qw(AnInterface AnotherInterface YetAnotherInterface); sub foo { my $self = shift; ... code ... };

      Which will be enough (as long as you call foo as a method) to ensure $self is of the appropriate class. Not verbose at all!

      I've argued elsewhere that if you don't call foo as a method you deserve all you get :-)

      So lets turn it around a bit:

      sub foo # requires interfaces InterfaceA, InterfaceB { my ($self) = @_; InterfaceA::validate($self); InterfaceB::validate($self); }

      This client code is much simpler: you are passing the implementation of the check down to a module that knows what it means to be that module. An you no longer require explicit inheritance:

      I'm covering the same ground as in Class::Interface -- isa() Considered Harmful - but surely these sort of relationships are exactly what ISA is all about?

      I don't understand what the advantage of avoiding explicit inheritance is? I've yet to see an example that wouldn't (in my opinion) be better handled by ISA relationships or delegation.

      Can somebody show me what I'm missing? :-)

        Can somebody show me what I'm missing? :-)

        Perhaps a good starting point would be to google C++ metatemplate programming. In fact, just consider any simple C++ template function, e.g.

        template<class T1, class T2> bool max(T1 a, T2 b) { return a < b ? b +: a; }
        Any object-pair that supports the less-than operator can be used with this function. There is no explicit C<Comparable> interface -- and there's no loss of static type safety. You ask: "I don't understand what the advantage of avoiding explicit inheritance is?": I'd turn that round: what advantage would be gained by requiring an explicit interface? Once you've convinced yourself that, in the context of C++, the answer is "none": then we can consider the context being Perl. --Dave
      I was thinking something along these lines while in the shower. (Best place to code) :P..

      Anywho... Thinking about the problem space, and what is needed, it just felt wholly unPerish to write the same or very similar code time and again.

      My actual thought was implementing a group of methods, possibly a seperate Class, which did validation. Then providing it a list of method names which needed to be within the inheritance tree. Something along the lines of maybe
      use Validate; ($ok, $err) = $obj->validate( isa => [this, that, theother], can_do => [meth1, meth2, meth3], ); croak $err if (!$ok);
      If they existed, then who cares who's calling. Then I thought "What if they override a prerequisite method within their own space"?.. Well if they do, then A) They know what they are doing, hence can use our code or B) They have no clue what they are doing, can't quite figure something out, or are trying to shutgun something together and deserve what ever quirks pop up.

      So I haven't really added anything to this thread, so I'm sorry. But I like the post Im replying to and its simplicity, and just wanted to pipe up that its how I was thinking of doing it as well, so an additional ++ to you cause I only have 1 vote for ya.


      /* And the Creator, against his better judgement, wrote man.c */
Re: OO-style modifiers for 'sub' ?
by jdporter (Paladin) on Jan 24, 2003 at 17:57 UTC
    IMO, what you're wanting to do is contrary to Perl's model of OO.
    In a nutshell, if a method can be called on an object, you should allow it, without enforcing specific class inheritance relationships. "Interfaces" in Perl are implicit.
    You might, of course, want to do extra type-checking on the arguments, as a way of getting polymorphism..

    jdporter
    The 6th Rule of Perl Club is -- There is no Rule #6.

      My issue with the way it works now, is that I can explicitly call a method by using it's full name (Class::Method), thereby bypassing the implicit passing of the first argument. I'd feel safe if there was an easy way to tell my method to do a solid type-check on the first argument, to ensure it's an object or class-name it can work with.

      Right now I do this with the same lines of code again and again, and I'm assuming I'm not the only one doing this kind of check. All the modifiers would add is an implicit check for a valid first argument, possibly pre-assigning it to a 'magic' variable, and leaving the actual arguments in @_...

      But as Aristotle pointed out, something quite similar can be done by using Attribute::Handlers. I would define an attribute handler for the 'Method', 'Class' and 'Instance' attributes...

        I agree though that the idea runs against the essence of Perl's general lots-of-rope attitude. And indeed as dragonchild pointed out you can shorten the whole thing to UNIVERSAL::isa($self, __PACKAGE__). But even that is problematic, as some other class may well offer the same methods and signatures as yours and therefor be safe, even though it doesn't inherit from yours. See Class::Interface -- isa() Considered Harmful for a thorough discussion of the subject.

        Of course, depending on your goals, it may be ok, but the caveats should be noted.

        Something you may want to look into is UNIVERSAL::can() which checks if a method given by name can be called on an object reference.

        Makeshifts last the longest.

        My issue with the way it works now, is that I can explicitly call a method by using it's full name (Class::Method), thereby bypassing the implicit passing of the first argument. I'd feel safe if there was an easy way to tell my method to do a solid type-check on the first argument, to ensure it's an object or class-name it can work with.

        It should be emphasised that:

        Class::method($self); # called as a subroutine $self->method; # called as a method

        are not equivalent. As soon as you start calling methods as subroutines you can't use SUPER:: or override in subclasses.

        Perhaps you want something like:

        use Devel::Caller qw(called_as_method); sub foo { my $self = shift; croak "I am an instance method" unless called_as_method(0); # actual body of code }

        Now foo croaks unless it is called as a method, which means $self->isa(__PACKAGE__) must be true.

        Personally, I wouldn't bother with checks like this. For me somebody calling an OO module in an non-OO style has deliberatly broken encapsulation - so deserves whatever chaos ensues :-)

        Update: the called_as_method should also be a fair bit faster since you don't have to do the potentially expensive walk of the inheritance tree.

Re: OO-style modifiers for 'sub' ?
by dragonchild (Archbishop) on Jan 24, 2003 at 18:06 UTC
    Personally, I'd do ... unless UNIVERSAL::isa($self, __PACKAGE__);, but that's just me.

    As for your ideas re: attributes ... they're good ideas. Come up with something, test it it, then submit it to CPAN for the community to use. *grins* It's the best thing about open-source!

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

Re: OO-style modifiers for 'sub' ?
by hardburn (Abbot) on Jan 24, 2003 at 17:45 UTC

    The really cool thing about Perl 5's object system is that it didn't require adding any new syntax. Admittedly, this doesn't make for the greatest object system, but it's somehow inspiring to know that OO Perl is just the combination of existing Perl structures.

    While your ideas could probably be done for Perl 6, I suggest just letting Perl 5 go without it if it means defining new syntax. Though if you can get the same thing in a module, go ahead and try.

      The really cool thing about Perl5's object system is that it didn't require adding any new syntax

      Apart from -> method invocation, indirect object syntax and SUPER:: that is ;-)

Re: OO-style modifiers for 'sub' ?
by trs80 (Priest) on Jan 24, 2003 at 21:00 UTC
    I am struggling more with the why on this one. Why would you want to do this? It seems you are, as you suggested, wanting Perl to behave too much like another language.

    So I can be more enlightened explain the benefit of doing the checks you mention.
      Good question... :)

      I guess I'm trying to make my code (modules) easier to use for both myself and others; if the the method is used incorrectly, and the program when runs tells me 'hey don't do that, you should use me in this and that way' it would cut down substantially on my debugging hours...

      But it's quite true that as soon as your code works, the whole checking becomes quite useless...

      Invoking the methods would still work exactly the same way, but if the 'modifiers' indicate that the method 'expects' a certain kind of first argument (be it class, object or either), the method could check to see if that's ok, and if not, croak.

      Almost every way of using the methods would still work. The only thing that might not, would be 'borrowing' methods from other modules/classes without using inheritancy. But I can't help feeling that doing something like that indicates a design flaw anyway...

      So yes, I would like to have the option to have Perl behave a bit like some other language... But then again, aren't most good things based in part on a collection of other good things? If I remember correctly, Larry was using sed and awk, but needed more... So he invented Perl and borrowed a bunch of the functionality from those tools amongst others. Which is great! I love it!

      It's not like I'm saying "Hey Perl should change, and everyone should do this!"... But traversing @ISA, and the UNIVERSAL class seem to be considered standard perl, and you are allowed, not obliged to use them... I would think being allowed (but not obliged) to use features such as the modifiers I suggested would further extend the freedom a Perl programmer has to build 'mean & clean' OO code...

      Everyone seems to be asking "Why do you want to be able to do this?"... Well, I would ask "Why would you not want to be able to do this?". Right now I'm doing it manually anyway... :)

      So either I'm just not getting what the 'evilness' is in my idea (even though I read every reply at least twice), or I just don't agree... :)

        I don't think your idea is evil in anyway. I like Perl because it is flexible and it lets you do what you want and assumes you know what you are doing.

        I can see where doing the type of checking you are talking about could limit your debugging time. That time savings would be increased as the number of developers having to interact with new code increases. Not knowing the coding environment it is difficult for anyone say if all the checks are really needed in the long run. It might be nice to be able to put the checks into their own method or function that could be overridden via configration variables, that is bypass the tests entirely under certain conditions.

        Another module that you might want to check out is Params::Validate.
        I am with the camp of "this is a waste of my time." Why? Because this is Perl, not Java. If i want someone to use my API correctly, i give them proper documentation. Yes, i suppose that nice friendly error messages would be nice ... but, the more Perl you program, the better you get at debugging.

        "as soon as your code works, the whole checking becomes quite useless..."

        That's right. The same goes for when you decide to change your design. Now you have extra baggage that you have to take care of to make sure that your 'bondage and discipline' code still works. No thanks. I'll gladly just make sure that my documentation is up to date.

        So, are you wasting your time by doing this? Of course not ... i am glad to you see trying to further your own knowledge of application design and OO in general. What i am saying is that i have been programming OO Perl for 2 years now (Java for 2 before that) and my applications do not suffer from any symptoms that you think might arrise from using my API wrong. It's really quite simple, if it is a circle, don't put a square in it - put a circle in it. If i really really really wanted something else, i'd be spending my days programming Java. This is Perl after all ...

        One more thing: (bold type added by me)

        "The only thing that might not, would be 'borrowing' methods from other modules/classes without using inheritancy. But I can't help feeling that doing something like that indicates a design flaw anyway..."

        Keep in mind that some think that using only inheritance is a design flaw ... OO newbies are always in a rush to solve every problem with inheritance and forget to consider aggregation.

        So, in closing - you asked "why would you not to be able to do this?" My answer - it's a waste of my time. It's (as dragonchild says) "borrowing trouble." But, please continue -- don't stop just because i think it is silly. After all, someone wrote Attribute::Handlers and Hook::WrapSub, so you aren't alone on this one. ;)

        jeffa

        L-LL-L--L-LL-L--L-LL-L--
        -R--R-RR-R--R-RR-R--R-RR
        B--B--B--B--B--B--B--B--
        H---H---H---H---H---H---
        (the triplet paradiddle with high-hat)
        
        Everyone seems to be asking "Why do you want to be able to do this?"... Well, I would ask "Why would you not want to be able to do this?". Right now I'm doing it manually anyway... :)

        Obviously, if you find it useful carry on doing it - I certainly don't think it's evil :-)

        However, if I was working in a team where people were regularly calling methods as Class::method($self) rather than $self->method I would immediately stop and spend some time teaching them some more about how perl works. The two are not equivalent, and using the former to call OO code is almost always a bad idea.

        Class::method($self) calls a subroutine method in package Class, and passes $self as the first argument.

        $self->method looks at the package $self is blessed into, uses this to find method in the inheritance hierarchy and execute it, passing $self as the first argument. Perl also keep enough context around so that we can call SUPER:: in method if necessary.

        By calling Class::method($self) we are:

        • explicitly breaking encapsulation by saying that method is implemented in package Class - setting yourself up for future problems if you push the method into another class during refactoring.
        • not treating $self as an object

        To me this is the moral equivalent in perl of, for example, doing pointer arithmetic to find a slot in a C++ object. Protecting against this kind of inappropriate usage is best done by educating developers (or hiring new ones), rather than by adding extra code.

        I don't worry about it in the same way I don't worry about people using Data::Dumper to get at my objects state. If somebody is that foolish they deserve all they get :-)

        I will happily argue for checking of method arguments - since this can make design decisions explicit. However, for me, calling object methods as methods, rather than subroutines, is a matter of correct use of perl and shouldn't need explicit checks.

Re: OO-style modifiers for 'sub' ?
by Anonymous Monk on Jan 27, 2003 at 07:44 UTC
    Perl 5.6+ already have a 'method' attribute for subroutines (sub f : method { }), but it currently doesn't do much afaik. You're feature could easily be turned into an attribute itself, which wraps the the method with code to check that $_[0] is from the proper class.

    On the other hand with good documentation for your code and a clear interface you shouldn't really need to check that a method is being called as a method anyways, because your interface should be consistent enough to make it obvious

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://229640]
Approved by footpad
Front-paged by footpad
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (1)
As of 2024-04-24 13:35 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found