Perl’s object system is fuzzy. Methods are really just subroutines and classes are just packages, which means that any subroutine in a package is also a method in that class. Your class might have subroutines that you’ve never even noticed, so you end up with methods that you didn’t want in your interface.
There are several reasons to not allow methods you don’t specifically want to create:
- If you don’t want the method, you shouldn’t have it. That’s the obvious one.
- If you don’t have the method in your API, your subclasses shouldn’t have it either.
- It’s organizationally messy, even if you never look under the hood.
- To some people, it’s morally suspicious if not a sin against Programming.
.
To begin, create a subroutine that shows you all of the subroutines defined in a package:
use 5.010; my @subs = get_defined_subs( 'main' ); say join "\n", @subs; sub get_defined_subs { my( $package ) = @_; my @subs; no strict 'refs'; foreach my $name ( keys %{"${package}::"} ) # symbol table magic { next unless defined &{"${package}::$name"}; push @subs, $name; } @subs }
Don’t worry about the symbol table magic there: that’s not the point of this Item. Try the subroutine yourself. With just that script, you see only the show_defined_subs
since that’s the only subroutine you’ve defined:
show_defined_subs
Now create a test class. Call it Class::Dirty
(since you are going to make a mess in it), and put that in Class/Dirty.pm in the same directory as your program so you find it in the current directory. To start, simply add a couple of methods to the class:
package Class::Dirty; sub new { ... } sub init { ... } 1;
Note that that literal code actually compiles under Perl 5.12, since that ...
is the “yadda yadda” operator that stands in for a real statement during compile time but dies at run time. That version isn’t quite out as this makes it to the website, but it’s Real Soon Now.
Now use show_defined_subs
with Class::Dirty
:
use Class::Dirty; my @subs = show_defined_subs( 'Class::Dirty' ); say join "\n\t", "Defined subs:", @subs; sub get_defined_subs { my( $package ) = @_; my @subs; no strict 'refs'; foreach my $name ( keys %{"${package}::"} ) { next unless defined &{"${package}::$name"}; push @subs, $name; } @subs }
As you might expect, you now see two subroutines names in the output. Those are the methods from Class::Dirty
:
Defined subs: new init
Since subroutines are also methods, instead of just checking that a subroutine is defined, check that the class responds to that method by checking can
. It’s a one line change:
use Class::Dirty; my @subs = get_defined_subs( 'Class::Dirty' ); say join "\n\t", "Available methods:", @subs; sub get_defined_subs { my( $package ) = @_; no strict 'refs'; foreach my $name ( keys %{"${package}::"} ) { next unless eval { $package->can( $name ) }; # <--- push @subs, $name; } @subs; }
You get the mostly same output because nothing has changed in Class::Dirty
. All of the subroutines it defines are also methods, so everything works out:
Available methods: new init
Now it's time to make a mess. Add the Fcntl
to your module and use the seek
set of constants (or anything else that exports stuff):
use 5.010; package Class::Dirty; use Fcntl qw(:seek); sub new { ... } sub init { ... }
Running the script again, you see that you have several new methods:
Defined subs: SEEK_SET new SEEK_END SEEK_CUR init
That's no good. Since Perl doesn't distinguish between subroutines and methods, any subroutine, even the imported one, are methods. You probably don't want that.
You could import nothing by using an empty import list and use the full path specification to get to anything you want to use but that's not very pretty:
use Fcntl (); ...; seek( $fh, 5, Fcntl::SEEK_CUR );
There's a better way to handle this though. The namespace::clean pragma can help. When you invoke it with use
, it remembers the names for the previously defined subroutines and schedules the unbinding of those names at the end of the scope (and remember what defines your scope). This module is a bit odd because it works with things that have already happened instead of things that are going to happen. You use
it when you want to unbind the subroutine names that you have already defined. In the Class::Dirty
case, you'd load it right after you load modules that exported names. Change your package name to Class::Clean
since it's no longer dirty:
package Class::Clean; use Fcntl qw(:seek); use namespace::clean; sub new { ... } sub init { ...; seek( $fh, 5, SEEK_SET ); # SEEK_SET still there }
The actual subroutines (the code, not the names) are still there and the code in the scope can still use these subroutines by calling them as subroutines, but they aren't available later as method calls because their names have disappeared and Perl won't be able to resolve the names to code at runtime.
How does that work? First, remember that subroutine names and the code that goes with them aren't the same thing. Represent the subroutines foo
and bar
in PeGS. The pointy boxes show the name bound to the boxes that have the actual code. The bar
subroutines calls the code that has the name foo
:
If you unbind the name from the subroutine after you compile, perl
doesn't care because it already knows how to get to the code:
The use namespace::clean
bit doesn't really do anything other than track the list of defined subroutines. The module doesn't actually remove them until the end of the compile cycle for its scope. That way, you can use the subroutine names in the code and perl
can use the names to find to the subroutine definitions. Once bound, however, perl
doesn't need the names any more because it already knows where the code is. When perl
gets to the end of the scope your during the compile phase, use namespace::clean
unbinds that list of names from the subroutine definitions.
It's easy to see this scoped behavior in action. Start without any blocks, and you can see that SEEK_SET
is still around:
package Clean; use strict; use warnings; use Fcntl qw(:seek); use namespace::clean; print "Seek set: ", SEEK_SET, "\n"; # SEEK_SET still there 1; # namespace::clean cleans up now, at end of file scope
Now use namespace::clean
in a naked block and the SEEK_SET
disappears at the end of that block:
package Class::Clean; use strict; use warnings; { use Fcntl qw(:seek); use namespace::clean; } # namespace::clean cleans up now, at end of block scope print "Seek set: ", SEEK_SET, "\n"; # Compilation error! 1;
This doesn't even compile because SEEK_SET
is now a bareword (where before Exporter
took care of all of that).
Now you want to define your methods, but you don't want those to disappear. After you've defined them when namespace::clean
does its work. to save them, invoke no namespace::clean
. It figures out what you've defined since the last use namespace::clean
and ingores those names so it won't unbind them later. Remember, namespace::clean
looks backward. After the no
, you can define more subroutines. If you invoke use namespace::clean
again, it remembers the names of those new subroutines for unbinding at the end of scope:
package Class::Clean; use strict; use warnings; # all of your imports go here use Fcntl qw(:seek); use namespace::clean; # unbind any names defined so far # namespace::clean list starts anew sub new { 1 }; sub init { 1 }; sub foo { _just_a_sub_not_a_method(); # called as sub, not method printf "SEEK_SET is %d\n", SEEK_SET; }; no namespace::clean; # don't unbind the latest list of names # namespace::clean list empty # all of your private subs (not private methods) go here sub _just_a_sub_not_a_method { # this is a normal sub print "I'm in _just_a_sub_not_a_method\n"; } use namespace::clean; # unbind the latest list of names 1;
Write a small program to test it:
use Class::Clean; print "I'm in main\n"; Class::Clean->foo; Class::Clean->_just_a_sub_not_a_method; # won't find this
That almost works. From foo
you can call _just_a_sub_not_a_method
because namespace::clean
had not already removed it when perl
compiled that part. At the end of the file, however, the name is gone, so when you call Class::Clean->_just_a_sub_not_a_method
, perl
can't resolve the method name.
I'm in main I'm in _just_a_sub_not_a_method SEEK_SET is 0 Can't locate object method "_just_a_sub_not_a_method" via package "Clean" at clean.pl line 7.
So there you have it. You don't have to let other modules create methods by importing into your namespace, and you can create private subroutines that only your scopes can access. You don't pass on a dirty API to any subclasses. Your Programming karma increases. Life is grand.