Welcome to Duncan White's Practical Software Development (PSD) Pages.
I'm Duncan White, an experienced and professional programmer, and have been programming for well over 20 years, mainly in C and Perl, although I know many other languages. In that time, despite my best intentions:-), I just can't help learning a thing or two about the practical matters of designing, programming, testing, debugging, running projects etc. Back in 2007, I thought I'd start writing an occasional series of articles, book reviews, more general thoughts etc, all focussing on software development without all the guff.
![]()
Perlstub Part 3: Another Perlstub extension
We could continue adding more facilities to perlstub - in particular, it would be nice to support Function::Parameters default parameters syntax, which involves 'parameter = defaultvalue' strings of the form:
- In the First Part of this article I showed the development of a very simple but useful vi-based editor extension called Perlstub.
- In the Second Part, we extended Perlstub to handle object oriented calls, as in:
my ($a,$b) = $object->instancemethod( parameters ) my $object = Class->classmethod( parameters )- Now in the final part (part 3), let's build another Perlstub extension: handling some cool new style function and method syntax.
- One of the newest and coolest (to me anyway) additions to Perl is the ability to manipulate the Perl parser on the fly using plugins. This ability has been exploited to add lovely new function and method syntax as an alternative to the horrible '@_' syntax. Two new (and independent) CPAN modules now exist adding similar new syntax, with (of course) some slight differences: Method::Signatures::Simple and Function::Parameters. They only work on relatively new versions of Perl (eg. Function::Parameters seems to need Perl 5.14).
- I'm going to choose to use Function::Parameters, if for no other reason than it having a more sensible module name! Having installed this CPAN module, you can use it by:
use Function::Parameters;- This adds three new pieces of syntax to Perl: first, you can declare subroutines via the new syntax:
fun wibble( $x, $y, @z ) { return $x * $y + scalar(@z); }This automatically breaks down '@_' into 'my($x,$y,@z)', totally does away with prototypes (which were never very good!), while still checking at run time that wibble is always called with 2 or more scalars.- Second, you can declare object instance methods via the new syntax:
method wibble( $w ) { $self->{W} = $w; }This automatically adds '$self' as an extra, hidden, first parameter, thus allowing the body to use '$self' as shown.- Third, you can declare class instance methods via the new syntax:
method wibble( $class: $w ) { return bless { W => $w }, $class; }This adds '$class' as an extra, hidden, first parameter (instead of '$self'), thus allowing the body to use '$class' as shown.- Now that I've started using this lovely new syntax, how do we make perlstub generate 'fun' and 'method' stubs?
- The first question is - should perlstub always generate new style stubs, or should it still be able to generate old-style 'sub' declarations too? Let's choose the latter. So, how should we detect whether to use new style or old style? There are several possibilities:
- If perlstub read the entire Perl file being edited, it could detect whether or not we'd imported Function::Parameters. But perlstub only reads the current stub declaration, so that possibility is out.
- We could key on the Perl version
$PERL_VERSION
, but that's pretty indirect.- We could try to find whether Function::Parameters is installed on the system.
- We could add a command line flag such as
--newstyle
. But then we'd have to edit our '~/.exrc' file to add the extra flag, and (if that's shared across many machines via NFS) you might want to edit Perl files on machines too old to support Function::Parameters so we'd have to either map two keys to different forms of perlstub invocation, or keep editing our '~/.exrc' file to switch it on and off.- Perhaps the simplest is to read an environment variable - and thus let the user choose, per session. Let's say that if the environment variable
PERLSTUB_STYLE
is set to 'fp' we'll generate new style 'fun' and 'method' declarations, otherwise we'll generate old style 'sub' declarations.- To set this environment variable, a csh/tcsh user would write:
setenv PERLSTUB_STYLE fpand a bash user would write:export PERLSTUB_STYLE=fp- So, we can now add the following code up near the top of perlstub:
# should we use new Function::Parameters style, or old 'sub' style? my $stylevar = $ENV{PERLSTUB_STYLE}; my $newstyle = defined $stylevar && $stylevar eq 'fp' ? 1 : 0;- Next, we observe that our previous addition of '$self' to the parameters (and '$' to the prototype) should not happen in new style - because the new 'method' syntax automatically adds '$self' - so we tweak our if condition to say:
if( $ismethod && ! $newstyle ) { unshift @params, $extraparam; # prepend extra leading parameter $protostr =~ s/^/\$/; # and one more leading scalar }- Next, how do we generate the new style declarations? Rather than have several complete sets of print statements, let's try to generalise what we have. Our current declaration generator is:
print "sub $funcname ($protostr)\n"; print "{\n"; print "\tmy( $paramstr ) = \@_;\n" if $paramstr; print "\t# STUB: write me...\n"; print "}\n\n\n";Tussock hopping again, we can first generalise this (without changing it's behaviour) by splitting out the conditional logic via a new variable '$mydec':my $mydec = ''; $mydec = "\tmy( $paramstr ) = \@_;\n" if $paramstr; print "sub $funcname ($protostr)\n"; print "{\n$mydec"; print "\t# STUB: write me...\n"; print "}\n\n\n";Now the block of print statements that generate the whole body is common to 'sub', 'fun' and 'method' - although the '$mydec' rules may need to be tweaked to handle new-style declarations. Apart from that, the 'sub' line is the only thing that needs to change, let's parameterise that too (again, first without changing the behaviour):my $mydec = ''; $mydec = "\tmy( $paramstr ) = \@_;\n" if $paramstr; my $firstline = "sub $funcname ($protostr)"; print "$firstline\n"; print "{\n$mydec"; print "\t# STUB: write me...\n"; print "}\n\n\n";- Now, we can add our new behaviour as several conditional statements modifying '$paramstr', '$mydec' and (especially) '$firstline':
$paramstr = '$class: ' . $paramstr if $newstyle && $ismethod && $isclass; ... my $mydec = ''; $mydec = "\tmy( $paramstr ) = \@_;\n" if $paramstr && ! $newstyle; my $firstline = "sub $funcname ($protostr)"; $firstline = "fun $funcname( $paramstr )" if $newstyle && ! $ismethod; $firstline = "method $funcname( $paramstr )" if $newstyle && $ismethod;- With this new code in place, and having enabled new-style via
setenv PERLSTUB_STYLE fp
, perlstub now generates the following five function/method declarations from our five examples:fun wibble( $x, $y, @z ) fun eek( $maxincs, $entry, %wibble ) fun hello( ) method instancemethod( $x, $y ) method classmethod( $class: $x, $y )whereas if we nowunsetenv PERLSTUB_STYLE
and rerun the same tests, the five declarations revert to their old form (but still work, especially worth checking the edge cases around instance and class methods after adding our various conditions:sub wibble ($$@) { my( $x, $y, @z ) = @_; ... sub eek ($$%) { my( $maxincs, $entry, %wibble ) = @_; ... sub hello () { ... sub instancemethod ($$$) { my( $self, $x, $y ) = @_; ... sub classmethod ($$$) { my( $class, $x, $y ) = @_; ...fun wibble( $x, $y = 1, @z = () )As long as the default values don't themselves contain commas, this should be straightforward. But I've left this as an exercise for the interested reader, because we have to stop somewhere.Conclusions
- My guiding principle of Ruthless automation applies to you and your editing techniques as well as your larger scale software engineering. If you find yourself doing something boring and repetitive in your favourite editor, seriously consider extending your editor to make your own life easier! Don't suffer, write a tool!
- Better still, building editor extension tools are much easier than you might expect. Our first functional version of perlstub comprised 80 lines of Perl, nearly half of which are comments, plus one mildly tricky vi 'map' command! Even our final version was only 104 lines of Perl. that's tiny!.
- Of course, perlstub is just an example, fixing something that irritated me. It doesn't matter if perlstub doesn't amaze you with it's sheer usefulness, as that wasn't really the point I'm trying to make, which was:
- First, you should build your own tools to simplify whatever irritates you.
- Second, that it's much easier than you may think!
d.white@imperial.ac.uk Back to my Practical Software Development Top Page. Written: Jan 2013