528 lines
18 KiB
Plaintext
528 lines
18 KiB
Plaintext
|
=pod
|
||
|
|
||
|
=for comment
|
||
|
DO NOT EDIT. This Pod was generated by Swim.
|
||
|
See http://github.com/ingydotnet/swim-pm#readme
|
||
|
|
||
|
=encoding utf8
|
||
|
|
||
|
=head1 NAME
|
||
|
|
||
|
Spiffy - Spiffy Perl Interface Framework For You
|
||
|
|
||
|
=for html
|
||
|
<a href="https://travis-ci.org/ingydotnet/spiffy-pm"><img src="https://travis-ci.org/ingydotnet/spiffy-pm.png" alt="spiffy-pm"></a>
|
||
|
<a href="https://coveralls.io/r/ingydotnet/spiffy-pm?branch=master"><img src="https://coveralls.io/repos/ingydotnet/spiffy-pm/badge.png" alt="spiffy-pm"></a>
|
||
|
|
||
|
=head1 SYNOPSIS
|
||
|
|
||
|
package Keen;
|
||
|
use Spiffy -Base;
|
||
|
field 'mirth';
|
||
|
const mood => ':-)';
|
||
|
|
||
|
sub happy {
|
||
|
if ($self->mood eq ':-(') {
|
||
|
$self->mirth(-1);
|
||
|
print "Cheer up!";
|
||
|
}
|
||
|
super;
|
||
|
}
|
||
|
|
||
|
=head1 DESCRIPTION
|
||
|
|
||
|
"Spiffy" is a framework and methodology for doing object oriented (OO)
|
||
|
programming in Perl. Spiffy combines the best parts of Exporter.pm, base.pm,
|
||
|
mixin.pm and SUPER.pm into one magic foundation class. It attempts to fix all
|
||
|
the nits and warts of traditional Perl OO, in a clean, straightforward and
|
||
|
(perhaps someday) standard way.
|
||
|
|
||
|
Spiffy borrows ideas from other OO languages like Python, Ruby, Java and Perl
|
||
|
6. It also adds a few tricks of its own.
|
||
|
|
||
|
If you take a look on CPAN, there are a ton of OO related modules. When
|
||
|
starting a new project, you need to pick the set of modules that makes most
|
||
|
sense, and then you need to use those modules in each of your classes. Spiffy,
|
||
|
on the other hand, has everything you'll probably need in one module, and you
|
||
|
only need to use it once in one of your classes. If you make Spiffy.pm the
|
||
|
base class of the basest class in your project, Spiffy will automatically pass
|
||
|
all of its magic to all of your subclasses. You may eventually forget that
|
||
|
you're even using it!
|
||
|
|
||
|
The most striking difference between Spiffy and other Perl object oriented
|
||
|
base classes, is that it has the ability to export things. If you create a
|
||
|
subclass of Spiffy, all the things that Spiffy exports will automatically be
|
||
|
exported by your subclass, in addition to any more things that you want to
|
||
|
export. And if someone creates a subclass of your subclass, all of those
|
||
|
things will be exported automatically, and so on. Think of it as "Inherited
|
||
|
Exportation", and it uses the familiar Exporter.pm specification syntax.
|
||
|
|
||
|
To use Spiffy or any subclass of Spiffy as a base class of your class, you
|
||
|
specify the C<-base> argument to the C<use> command.
|
||
|
|
||
|
use MySpiffyBaseModule -base;
|
||
|
|
||
|
You can also use the traditional C<use base 'MySpiffyBaseModule';> syntax and
|
||
|
everything will work exactly the same. The only caveat is that Spiffy.pm must
|
||
|
already be loaded. That's because Spiffy rewires base.pm on the fly to do all
|
||
|
the Spiffy magics.
|
||
|
|
||
|
Spiffy has support for Ruby-like mixins with Perl6-like roles. Just like
|
||
|
C<base> you can use either of the following invocations:
|
||
|
|
||
|
use mixin 'MySpiffyBaseModule';
|
||
|
use MySpiffyBaseModule -mixin;
|
||
|
|
||
|
The second version will only work if the class being mixed in is a subclass of
|
||
|
Spiffy. The first version will work in all cases, as long as Spiffy has
|
||
|
already been loaded.
|
||
|
|
||
|
To limit the methods that get mixed in, use roles. (Hint: they work just like
|
||
|
an Exporter list):
|
||
|
|
||
|
use MySpiffyBaseModule -mixin => qw(:basics x y !foo);
|
||
|
|
||
|
In object oriented Perl almost every subroutine is a method. Each method gets
|
||
|
the object passed to it as its first argument. That means practically every
|
||
|
subroutine starts with the line:
|
||
|
|
||
|
my $self = shift;
|
||
|
|
||
|
Spiffy provides a simple, optional filter mechanism to insert that line for
|
||
|
you, resulting in cleaner code. If you figure an average method has 10 lines
|
||
|
of code, that's 10% of your code! To turn this option on, you just use the C<-
|
||
|
Base> option instead of the C<-base> option, or add the C<-selfless> option.
|
||
|
If source filtering makes you queazy, don't use the feature. I personally find
|
||
|
it addictive in my quest for writing squeaky clean, maintainable code.
|
||
|
|
||
|
A useful feature of Spiffy is that it exports two functions: C<field> and
|
||
|
C<const> that can be used to declare the attributes of your class, and
|
||
|
automatically generate accessor methods for them. The only difference between
|
||
|
the two functions is that C<const> attributes can not be modified; thus the
|
||
|
accessor is much faster.
|
||
|
|
||
|
One interesting aspect of OO programming is when a method calls the same
|
||
|
method from a parent class. This is generally known as calling a super method.
|
||
|
Perl's facility for doing this is butt ugly:
|
||
|
|
||
|
sub cleanup {
|
||
|
my $self = shift;
|
||
|
$self->scrub;
|
||
|
$self->SUPER::cleanup(@_);
|
||
|
}
|
||
|
|
||
|
Spiffy makes it, er, super easy to call super methods. You just use the
|
||
|
C<super> function. You don't need to pass it any arguments because it
|
||
|
automatically passes them on for you. Here's the same function with Spiffy:
|
||
|
|
||
|
sub cleanup {
|
||
|
$self->scrub;
|
||
|
super;
|
||
|
}
|
||
|
|
||
|
Spiffy has a special method for parsing arguments called C<parse_arguments>,
|
||
|
that it also uses for parsing its own arguments. You declare which arguments
|
||
|
are boolean (singletons) and which ones are paired, with two special methods
|
||
|
called C<boolean_arguments> and C<paired_arguments>. Parse arguments pulls out
|
||
|
the booleans and pairs and returns them in an anonymous hash, followed by a
|
||
|
list of the unmatched arguments.
|
||
|
|
||
|
Finally, Spiffy can export a few debugging functions C<WWW>, C<XXX>, C<YYY>
|
||
|
and C<ZZZ>. Each of them produces a YAML dump of its arguments. WWW warns the
|
||
|
output, XXX dies with the output, YYY prints the output, and ZZZ confesses the
|
||
|
output. If YAML doesn't suit your needs, you can switch all the dumps to
|
||
|
Data::Dumper format with the C<-dumper> option.
|
||
|
|
||
|
That's Spiffy!
|
||
|
|
||
|
=head1 EXPORTING
|
||
|
|
||
|
Spiffy implements a completely new idea in Perl. Modules that act both as
|
||
|
object oriented classes and that also export functions. But it takes the
|
||
|
concept of Exporter.pm one step further; it walks the entire C<@ISA> path of a
|
||
|
class and honors the export specifications of each module. Since Spiffy calls
|
||
|
on the Exporter module to do this, you can use all the fancy interface
|
||
|
features that Exporter has, including tags and negation.
|
||
|
|
||
|
Spiffy considers all the arguments that don't begin with a dash to comprise
|
||
|
the export specification.
|
||
|
|
||
|
package Vehicle;
|
||
|
use Spiffy -base;
|
||
|
our $SERIAL_NUMBER = 0;
|
||
|
our @EXPORT = qw($SERIAL_NUMBER);
|
||
|
our @EXPORT_BASE = qw(tire horn);
|
||
|
|
||
|
package Bicycle;
|
||
|
use Vehicle -base, '!field';
|
||
|
$self->inflate(tire);
|
||
|
|
||
|
In this case, C<< Bicycle->isa('Vehicle') >> and also all the things that
|
||
|
C<Vehicle> and C<Spiffy> export, will go into C<Bicycle>, except C<field>.
|
||
|
|
||
|
Exporting can be very helpful when you've designed a system with hundreds of
|
||
|
classes, and you want them all to have access to some functions or constants
|
||
|
|
||
|
or variables. Just export them in your main base class and every subclass
|
||
|
|
||
|
will get the functions they need.
|
||
|
|
||
|
You can do almost everything that Exporter does because Spiffy delegates the
|
||
|
job to Exporter (after adding some Spiffy magic). Spiffy offers a
|
||
|
C<@EXPORT_BASE> variable which is like C<@EXPORT>, but only for usages that
|
||
|
use C<-base>.
|
||
|
|
||
|
=head1 MIXINS & ROLES
|
||
|
|
||
|
If you've done much OO programming in Perl you've probably used Multiple
|
||
|
Inheritance (MI), and if you've done much MI you've probably run into weird
|
||
|
problems and headaches. Some languages like Ruby, attempt to resolve MI
|
||
|
issues using a technique called mixins. Basically, all Ruby classes use only
|
||
|
Single Inheritance (SI), and then I<mixin> functionality from other modules
|
||
|
if they need to.
|
||
|
|
||
|
Mixins can be thought of at a simplistic level as I<importing> the methods of
|
||
|
another class into your subclass. But from an implementation standpoint that's
|
||
|
not the best way to do it. Spiffy does what Ruby does. It creates an empty
|
||
|
anonymous class, imports everything into that class, and then chains the new
|
||
|
class into your SI ISA path. In other words, if you say:
|
||
|
|
||
|
package AAA;
|
||
|
use BBB -base;
|
||
|
use CCC -mixin;
|
||
|
use DDD -mixin;
|
||
|
|
||
|
You end up with a single inheritance chain of classes like this:
|
||
|
|
||
|
AAA << AAA-DDD << AAA-CCC << BBB;
|
||
|
|
||
|
C<AAA-DDD> and C<AAA-CCC> are the actual package names of the generated
|
||
|
classes. The nice thing about this style is that mixing in CCC doesn't clobber
|
||
|
any methods in AAA, and DDD doesn't conflict with AAA or CCC either. If you
|
||
|
mixed in a method in CCC that was also in AAA, you can still get to it by
|
||
|
using C<super>.
|
||
|
|
||
|
When Spiffy mixes in CCC, it pulls in all the methods in CCC that do not begin
|
||
|
with an underscore. Actually it goes farther than that. If CCC is a subclass
|
||
|
it will pull in every method that CCC C<can> do through inheritance. This is
|
||
|
very powerful, maybe too powerful.
|
||
|
|
||
|
To limit what you mixin, Spiffy borrows the concept of Roles from Perl6. The
|
||
|
term role is used more loosely in Spiffy though. It's much like an import list
|
||
|
that the Exporter module uses, and you can use groups (tags) and negation. If
|
||
|
the first element of your list uses negation, Spiffy will start with all the
|
||
|
methods that your mixin class can do.
|
||
|
|
||
|
use EEE -mixin => qw(:tools walk !run !:sharp_tools);
|
||
|
|
||
|
In this example, C<walk> and C<run> are methods that EEE can do, and C<tools>
|
||
|
and C<sharp_tools> are roles of class EEE. How does class EEE define these
|
||
|
roles? It very simply defines methods called C<_role_tools> and
|
||
|
C<_role_sharp_tools> which return lists of more methods. (And possibly other
|
||
|
roles!) The neat thing here is that since roles are just methods, they too can
|
||
|
be inherited. Take B<that> Perl6!
|
||
|
|
||
|
=head1 FILTERING
|
||
|
|
||
|
By using the C<-Base> flag instead of C<-base> you never need to write the
|
||
|
line:
|
||
|
|
||
|
my $self = shift;
|
||
|
|
||
|
This statement is added to every subroutine in your class by using a source
|
||
|
filter. The magic is simple and fast, so there is litte performance penalty
|
||
|
for creating clean code on par with Ruby and Python.
|
||
|
|
||
|
package Example;
|
||
|
use Spiffy -Base;
|
||
|
|
||
|
sub crazy {
|
||
|
$self->nuts;
|
||
|
}
|
||
|
sub wacky { }
|
||
|
sub new() {
|
||
|
bless [], shift;
|
||
|
}
|
||
|
|
||
|
is exactly the same as:
|
||
|
|
||
|
package Example;
|
||
|
use Spiffy -base;
|
||
|
use strict;use warnings;
|
||
|
sub crazy {my $self = shift;
|
||
|
$self->nuts;
|
||
|
}
|
||
|
sub wacky {my $self = shift; }
|
||
|
sub new {
|
||
|
bless [], shift;
|
||
|
}
|
||
|
;1;
|
||
|
|
||
|
Note that the empty parens after the subroutine C<new> keep it from having a
|
||
|
$self added. Also note that the extra code is added to existing lines to
|
||
|
ensure that line numbers are not altered.
|
||
|
|
||
|
C<-Base> also turns on the strict and warnings pragmas, and adds that annoying
|
||
|
'1;' line to your module.
|
||
|
|
||
|
=head1 PRIVATE METHODS
|
||
|
|
||
|
Spiffy now has support for private methods when you use the '-Base' filter
|
||
|
mechanism. You just declare the subs with the C<my> keyword, and call them
|
||
|
with a C<'$'> in front. Like this:
|
||
|
|
||
|
package Keen;
|
||
|
use SomethingSpiffy -Base;
|
||
|
|
||
|
# normal public method
|
||
|
sub swell {
|
||
|
$self->$stinky;
|
||
|
}
|
||
|
|
||
|
# private lexical method. uncallable from outside this file.
|
||
|
my sub stinky {
|
||
|
...
|
||
|
}
|
||
|
|
||
|
=head1 SPIFFY DEBUGGING
|
||
|
|
||
|
The XXX function is very handy for debugging because you can insert it almost
|
||
|
anywhere, and it will dump your data in nice clean YAML. Take the following
|
||
|
statement:
|
||
|
|
||
|
my @stuff = grep { /keen/ } $self->find($a, $b);
|
||
|
|
||
|
If you have a problem with this statement, you can debug it in any of the
|
||
|
following ways:
|
||
|
|
||
|
XXX my @stuff = grep { /keen/ } $self->find($a, $b);
|
||
|
my @stuff = XXX grep { /keen/ } $self->find($a, $b);
|
||
|
my @stuff = grep { /keen/ } XXX $self->find($a, $b);
|
||
|
my @stuff = grep { /keen/ } $self->find(XXX $a, $b);
|
||
|
|
||
|
XXX is easy to insert and remove. It is also a tradition to mark uncertain
|
||
|
areas of code with XXX. This will make the debugging dumpers easy to spot if
|
||
|
you forget to take them out.
|
||
|
|
||
|
WWW and YYY are nice because they dump their arguments and then return the
|
||
|
arguments. This way you can insert them into many places and still have the
|
||
|
code run as before. Use ZZZ when you need to die with both a YAML dump and a
|
||
|
full stack trace.
|
||
|
|
||
|
The debugging functions are exported by default if you use the C<-base>
|
||
|
option, but only if you have previously used the C<-XXX> option. To export all
|
||
|
4 functions use the export tag:
|
||
|
|
||
|
use SomeSpiffyModule ':XXX';
|
||
|
|
||
|
To force the debugging functions to use Data::Dumper instead of YAML:
|
||
|
|
||
|
use SomeSpiffyModule -dumper;
|
||
|
|
||
|
=head1 SPIFFY FUNCTIONS
|
||
|
|
||
|
This section describes the functions the Spiffy exports. The C<field>,
|
||
|
C<const>, C<stub> and C<super> functions are only exported when you use the
|
||
|
C<-base> or C<-Base> options.
|
||
|
|
||
|
=over
|
||
|
|
||
|
=item field
|
||
|
|
||
|
Defines accessor methods for a field of your class:
|
||
|
|
||
|
package Example;
|
||
|
use Spiffy -Base;
|
||
|
|
||
|
field 'foo';
|
||
|
field bar => [];
|
||
|
|
||
|
sub lalala {
|
||
|
$self->foo(42);
|
||
|
push @{$self->{bar}}, $self->foo;
|
||
|
}
|
||
|
|
||
|
The first parameter passed to C<field> is the name of the attribute being
|
||
|
defined. Accessors can be given an optional default value. This value will be
|
||
|
returned if no value for the field has been set in the object.
|
||
|
|
||
|
=item const
|
||
|
|
||
|
const bar => 42;
|
||
|
|
||
|
The C<const> function is similar to <field> except that it is immutable.
|
||
|
It also does not store data in the object. You probably always want to
|
||
|
give a C<const> a default value, otherwise the generated method will be
|
||
|
somewhat useless.
|
||
|
|
||
|
=item stub
|
||
|
|
||
|
stub 'cigar';
|
||
|
|
||
|
The C<stub> function generates a method that will die with an appropriate
|
||
|
message. The idea is that subclasses must implement these methods so that the
|
||
|
stub methods don't get called.
|
||
|
|
||
|
=item super
|
||
|
|
||
|
If this function is called without any arguments, it will call the same method
|
||
|
that it is in, higher up in the ISA tree, passing it all the same arguments.
|
||
|
If it is called with arguments, it will use those arguments with C<$self> in
|
||
|
the front. In other words, it just works like you'd expect.
|
||
|
|
||
|
sub foo {
|
||
|
super; # Same as $self->SUPER::foo(@_);
|
||
|
super('hello'); # Same as $self->SUPER::foo('hello');
|
||
|
$self->bar(42);
|
||
|
}
|
||
|
|
||
|
sub new() {
|
||
|
my $self = super;
|
||
|
$self->init;
|
||
|
return $self;
|
||
|
}
|
||
|
|
||
|
C<super> will simply do nothing if there is no super method. Finally, C<super>
|
||
|
does the right thing in AUTOLOAD subroutines.
|
||
|
|
||
|
=back
|
||
|
|
||
|
=head1 METHODS
|
||
|
|
||
|
This section lists all of the methods that any subclass of Spiffy
|
||
|
automatically inherits.
|
||
|
|
||
|
=over
|
||
|
|
||
|
=item mixin
|
||
|
|
||
|
A method to mixin a class at runtime. Takes the same arguments as C<use mixin
|
||
|
...>. Makes the target class a mixin of the caller.
|
||
|
|
||
|
$self->mixin('SomeClass');
|
||
|
$object->mixin('SomeOtherClass' => 'some_method');
|
||
|
|
||
|
=item parse_arguments
|
||
|
|
||
|
This method takes a list of arguments and groups them into pairs. It allows
|
||
|
for boolean arguments which may or may not have a value (defaulting to 1).
|
||
|
The method returns a hash reference of all the pairs as keys and values in
|
||
|
the hash. Any arguments that cannot be paired, are returned as a list. Here
|
||
|
is an example:
|
||
|
|
||
|
sub boolean_arguments { qw(-has_spots -is_yummy) }
|
||
|
sub paired_arguments { qw(-name -size) }
|
||
|
my ($pairs, @others) = $self->parse_arguments(
|
||
|
'red', 'white',
|
||
|
-name => 'Ingy',
|
||
|
-has_spots =>
|
||
|
-size => 'large',
|
||
|
'black',
|
||
|
-is_yummy => 0,
|
||
|
);
|
||
|
|
||
|
After this call, C<$pairs> will contain:
|
||
|
|
||
|
{
|
||
|
-name => 'Ingy',
|
||
|
-has_spots => 1,
|
||
|
-size => 'large',
|
||
|
-is_yummy => 0,
|
||
|
}
|
||
|
|
||
|
and C<@others> will contain 'red', 'white', and 'black'.
|
||
|
|
||
|
=item boolean_arguments
|
||
|
|
||
|
Returns the list of arguments that are recognized as being boolean. Override
|
||
|
this method to define your own list.
|
||
|
|
||
|
=item paired_arguments
|
||
|
|
||
|
Returns the list of arguments that are recognized as being paired. Override
|
||
|
this method to define your own list.
|
||
|
|
||
|
=back
|
||
|
|
||
|
=head1 ARGUMENTS
|
||
|
|
||
|
When you C<use> the Spiffy module or a subclass of it, you can pass it a list
|
||
|
of arguments. These arguments are parsed using the C<parse_arguments> method
|
||
|
described above. The special argument C<-base>, is used to make the current
|
||
|
package a subclass of the Spiffy module being used.
|
||
|
|
||
|
Any non-paired parameters act like a normal import list; just like those used
|
||
|
with the Exporter module.
|
||
|
|
||
|
=head1 USING SPIFFY WITH BASE.PM
|
||
|
|
||
|
The proper way to use a Spiffy module as a base class is with the C<-base>
|
||
|
parameter to the C<use> statement. This differs from typical modules where you
|
||
|
would want to C<use base>.
|
||
|
|
||
|
package Something;
|
||
|
use Spiffy::Module -base;
|
||
|
use base 'NonSpiffy::Module';
|
||
|
|
||
|
Now it may be hard to keep track of what's Spiffy and what is not. Therefore
|
||
|
Spiffy has actually been made to work with base.pm. You can say:
|
||
|
|
||
|
package Something;
|
||
|
use base 'Spiffy::Module';
|
||
|
use base 'NonSpiffy::Module';
|
||
|
|
||
|
C<use base> is also very useful when your class is not an actual module (a
|
||
|
separate file) but just a package in some file that has already been loaded.
|
||
|
C<base> will work whether the class is a module or not, while the C<-base>
|
||
|
syntax cannot work that way, since C<use> always tries to load a module.
|
||
|
|
||
|
=head2 base.pm Caveats
|
||
|
|
||
|
To make Spiffy work with base.pm, a dirty trick was played. Spiffy swaps
|
||
|
C<base::import> with its own version. If the base modules are not Spiffy,
|
||
|
Spiffy calls the original base::import. If the base modules are Spiffy, then
|
||
|
Spiffy does its own thing.
|
||
|
|
||
|
There are two caveats.
|
||
|
|
||
|
=over
|
||
|
|
||
|
=item Spiffy must be loaded first.
|
||
|
|
||
|
If Spiffy is not loaded and C<use base> is invoked on a Spiffy module, Spiffy
|
||
|
will die with a useful message telling the author to read this documentation.
|
||
|
That's because Spiffy needed to do the import swap beforehand.
|
||
|
|
||
|
If you get this error, simply put a statement like this up front in your code:
|
||
|
|
||
|
use Spiffy ();
|
||
|
|
||
|
=item No Mixing
|
||
|
|
||
|
C<base.pm> can take multiple arguments. And this works with Spiffy as long as
|
||
|
all the base classes are Spiffy, or they are all non-Spiffy. If they are
|
||
|
mixed, Spiffy will die. In this case just use separate C<use base> statements.
|
||
|
|
||
|
=back
|
||
|
|
||
|
=head1 SPIFFY TODO LIST
|
||
|
|
||
|
Spiffy is a wonderful way to do OO programming in Perl, but it is still a work
|
||
|
in progress. New things will be added, and things that don't work well, might
|
||
|
be removed.
|
||
|
|
||
|
=head1 AUTHOR
|
||
|
|
||
|
Ingy döt Net <ingy@cpan.org>
|
||
|
|
||
|
=head1 COPYRIGHT AND LICENSE
|
||
|
|
||
|
Copyright 2004-2014. Ingy döt Net.
|
||
|
|
||
|
This program is free software; you can redistribute it and/or modify it under
|
||
|
the same terms as Perl itself.
|
||
|
|
||
|
See L<http://www.perl.com/perl/misc/Artistic.html>
|
||
|
|
||
|
=cut
|