Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Private method variations

by adrianh (Chancellor)
on Mar 01, 2004 at 00:42 UTC ( [id://332744]=perlmeditation: print w/replies, xml ) Need Help??

While tidying up some code recently I was pondering the problems with the use of the _method_name() convention to indicate private methods in Perl.

Of course their are many alternative conventions that prevent the accidental overriding of methods in subclasses, for example:

package Foo; my $_private = sub { ... }; my _private = sub { ... }; sub foo { my $self = shift; # we can specify the full package name $self->Foo::_private(); # we can call as a subroutine _private($self); # we can use lexically scoped subs $self->$_private(); };

They also all have disadvantages of one sort or another, for example:

  • LongPackageNamesCanGetSomewhatTediousToType
  • Using subroutine calling style makes it harder to switch to/from private/public methods during development
  • With PackageNames and subroutine calls the method is still visible in subclasses
  • When you use lexically scoped subroutines you have to be share your method name space with your other lexical scalars

With the package name and subroutine calling methods we can prevent the method from being inherited by subclasses by putting it in a seperate package (see (tye)Re: Private Class Methods for an example). So we can do things like:

package Foo; sub Foo::private::method { ... }; sub foo { my $self = shift; $self->Foo::private::method { ... }; };

Which is nice, but we still have to repeat the Foo::private package every time we call the method - which for VeryLongPackageNames could be tedious.

A convenient shortcut to the private method space would be nice. Maybe something like:

sub Foo::MY::method { ... }; sub foo { my $self = shift; $self->MY::method(); };

This fits in quite nicely with SUPER:: and NEXT::.

This is actually pretty trivial to implement - just stick an AUTOLOAD in the MY package:

package MY; sub AUTOLOAD { my $self = shift; our $AUTOLOAD; my $method = caller() . "::$AUTOLOAD"; $self->$method(@_); };

and Bob's the parental sibling of your choice.

Unfortunately this adds an extra method call worth of overhead to every private method. Probably a bad move - Perl's method invocation is slow enough as it is.

It would also be nice to be able to do:

sub MY::method { ... };

rather than

sub MyLongPackageName::MY::method { ... };

Having to repeat MyLongPackageName for every private method definition is a pain.

Looks like a job for (and I don't say this very often because they're evil :-) a source filter.

package MY; use strict; use warnings; my $Imported_from; sub import { $Imported_from = caller }; use Filter::Simple; FILTER_ONLY code => sub { s/(\w+::)*MY::/${Imported_from}::MY::/gs }; 1;

Which will allow us to write code like this:

package Foo; use MY; sub new { bless {}, shift }; sub hello { my $self = shift; $self->MY::greet_world("hi"); }; sub MY::greet_world { my ($self, $greeting) = @_; print "$greeting world\n"; }; package Bar; use base qw(Foo); use MY; sub new { my $class = shift; $class->MY::greet_world(); return $class->SUPER::new(@_); }; sub MY::greet_world { print "A new Bar has entered the world\n"; };

Without the two private greet_world() methods of Foo and Bar interfering, and without any run-time overhead.

Neat.

Worth throwing at CPAN?

Replies are listed 'Best First'.
Re: Private method variations
by TimToady (Parson) on Mar 01, 2004 at 05:52 UTC
    You should probably be aware that the MY:: package name will be reserved in Perl 6 for getting at your current lexical scope.

    On the other hand, your approach isn't so very different from that which Perl 6 will take--private methods and attributes will essentially be off in their own chunk of namespace. Of course, Perl 6 won't need to use source filtering for that...

    Anyway, you're on the right track. Now if I could convince you to use My instead of MY, your scripts will be slightly easier to translate to Perl 6.

      Now if I could convince you to use My instead of MY, your scripts will be slightly easier to translate to Perl 6.

      Actually, how about OUR:: instead of My:: - or is that somewhere in Perl 6 too?

      Reasoning:

      • Keeps the upper case convention of SUPER and NEXT
      • Still nice and short to type
      • Still the same sort of English meaning (this method is ours and cannot be used by other classes)
      • Functionality is closer to our than my in Perl. We're messing with the (apparent) scoping of a package variable, not a lexical.

      Sound vaguely sane?

        Actually, how about OUR:: instead of My:: - or is that somewhere in Perl 6 too?
        Well, I actually thought about that one, but didn't think you'd go for something even longer. :-)

        As for whether it's in Perl 6, the only answer I can give is: "not yet". But by and large all-uppercase names are kinda sorta reserved for Perl to grow into. More or less. So maybe Our:: would be a better choice.

        Maybe I am being niave but, is there something wrong with MyClass::PRIVATE:: ? It would be the least ambiguous of all IMO. I mean it would be hard to grab the PRIVATE:: root namespace, but if you are using a source filter anyway, you could generate the MyClass::PRIVATE:: "inner"-package with ease.

        -stvn
      You should probably be aware that the MY:: package name will be reserved in Perl 6 for getting at your current lexical scope.

      Bah. And I knew that too.

      Now if I could convince you to use My instead of MY, your scripts will be slightly easier to translate to Perl 6.

      Colour me convinced :-)

Re: Private method variations
by dragonchild (Archbishop) on Mar 01, 2004 at 02:35 UTC
    How is SUPER implemented? I think it would make more sense to leverage that code and make it part of Perl itself.

    An alternative I prefer better is to make it an attribute. Something along the lines of:

    sub greet_world : no_override { my $self = shift; my ($greeting) = @_; print "$greeting, world\n"; }

    Of course, I don't know a lot about attributes, but that would make more syntactical sense, to me ...

    Update: Would Attribute::Protected do this?

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

    Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

      How is SUPER implemented? I think it would make more sense to leverage that code and make it part of Perl itself.

      I doubt there would be much code to leverage. Beyond the lexical similarity there's not a lot of common ground. SUPER is doing a runtime method lookup, while MY is doing compile-time namespace fiddling.

      An alternative I prefer better is to make it an attribute

      You could use an attribute to wrap a method to check it was only called on objects of a specific type. However this would still leave the method in the same name space, which leads to the original problem of subclasses breaking due to implementation changes in the base class.

      You could also use the attribute, I guess, to mark methods for later transfer to another namespace in an INIT block - but that would still leave the method calls to be dealt with. You'd have to use a source filter for that - or use the full package name of the private namespace which CanBeTedious.

      Of course in Perl 6 it would both trivial (since we have a decent macro system) and unnecessary (since Larry and friends are making what looks to be a very nice OO environment).

      Problem is, that is run-time, not compile-time stuff.

      When called from inappropriate classes, those methods throw an exception like foo() is a protected method of Foo!.

      Can't say I like that. I want to know if my system is broken before I happen to stumble across a particular leg of code -- especially if it's not going to be covered very often.

      Yes, some one else is going to chime in and say "Perl OO isn't like that", and I'll say "why shouldn't it be?". Hint: that's a rhetorical question.

        I agree with you, that its nice to know at compile time, but perl isn't like tha...

        Yes, some one else is going to chime in and say "Perl OO isn't like that", and I'll say "why shouldn't it be?". Hint: that's a rhetorical question.

        Its not so much that Perl is not like that, its that the more "dynamic" OO languages are not like that. Smalltalk, Python, etc. etc. most of them don't have a concept of public, protected and private either. It's really an idea that is much more acceptable in a more "static" OO language where such "restrictions" are the norm. It's a trade-off really.

        Personally, I am waiting to see how they solve issues like this in the Perl 6 object model. It should be very interesting.

        -stvn
Re: Private method variations
by flyingmoose (Priest) on Mar 01, 2004 at 01:36 UTC
    I'm rather new to Perl OO-ness (but I have significant experience in other languages), but I must say, I like this idea very very much. It fixes one of my biggest issues with Perl OO -- enforcing encapsulation requires some huge incantations that make the code rather hard to read.

    Just thinking aloud, but could something similar be done to enforce private variables? This would be a cool feature to have, even if strangely implemented. Hopefully in the same module?

    Even if some extra private-variable functionality was not added, I can see how this could trivially be used to restrict an accessor/mutator function. Generally I try to write my OO such that trivial accessor/mutators aren't needed (too many accessors/mutators tends to indicate design flaws), but in Perl, the accessor/mutators are somewhat useful since it's way too easy to create a typo in a hash lookup.

    Also, source filters, from what I know of them, are supposed to be evil...but as long as this one doesn't bork with line numbers, it should be ok! Seems like goodness to me.

    Cool. I'd say definitely put something like this on CPAN. I'd use it.

      Just thinking aloud, but could something similar be done to enforce private variables? This would be a cool feature to have, even if strangely implemented. Hopefully in the same module?

      Have a look at previous nodes on inside out objects. They're a nice technique Abigail-II came up with for doing private variables. As a bonus they prevent typos too.

      Well, normally "private" variables are named with an "_" at the beginning as a key in the blessed hashref...
      since the blessed object is "just" a normal data structure in perl, it is always possible to access the data within that structure... but why does the user who is using your module want to kill himself by not using your methods but accessing the datastructure itself?!?
      On your hand, Perl OO is not as other OO programming like in C++ or java. The blessed "thingy" is not your class object but just an object within your class. so it can not know anything about public or private.

      Hope, i got it right!

      Update:Daim... i missed the original post...sorry...stupid mistake...
        On your hand, Perl OO is not as other OO programming like in C++ or java

        It's a good thing that it's not like C++ (restrictive) or Java (broken), but I still say there is room for improvement. Using underscores is cheesy and was one of the reasons I scoff at Python when people claim it has a good OO model.

Re: Private method variations
by hardburn (Abbot) on Mar 01, 2004 at 14:23 UTC

    If you want to avoid namespace clashes, you can just avoid the method lookup entirely and pass the object as the first parameter to the private method (this happens anyway, we just aren't faking it). As a bonus, the subroutine lookup time is reduced:

    my $private1 = sub { print "Private1\n" }; sub _private2 { print "Private2\n" } sub public_method { my $self = shift; $private1->($self); _private2($self); }

    Calling like this shouldn't be done for public and protected methods because it breaks subclassing, but private methods aren't supposed to be subclassed anyway.

    ----
    : () { :|:& };:

    Note: All code is untested, unless otherwise stated

      If you want to avoid namespace clashes, you can just avoid the method lookup entirely and pass the object as the first parameter to the private method

      This option was mentioned several times in the OP you know :-)

      Two problems:

      • Using a different calling style makes it harder to switch to/from private/public methods since we have to change the way $self is passed
      • The method name is still in the main namespace, so subclasses can call it accidentally leading to potentially odd results
        Nevertheless, it's the use of the same calling style that is the root of the problem.

        Perl 6 will have a compromise solution to this. The calling style for a private method is very like, but not quite the same as, the calling style for a public method. All private attributes and method names will start with a distinguishing character, which at the moment I would guess is colon, though at one time it was exclamation mark. Anyway,

        has $.foo; # public attribute has $:bar; # private attribute method foo (){...} # public method method :bar () {...} # private method ... $self.foo() # always calls public method virtually .foo() # unary variant $self.:bar() # always calls private method .:bar() # unary variant ... $self.bar() # calls public bar, not private.
        In this way, you don't have to change your argument passing--it's just a matter of inserting an extra character. The calls are also self-documenting, since you can tell by inspection whether you're looking for something public or private. Likewise, any use of $.foo is obviously public, while any use of $:foo is obviously private.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others scrutinizing the Monastery: (5)
As of 2024-03-19 02:21 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found