Johan Lindstrom
Posted on June 12, 2019
Emacs has this cool package yasnippet
, which is a simple template expansion system.
So for instance when I'm declaring a method, I can type a simple trigger word: argsm
(args for a method), hit <Tab>
and the trigger word expands into the corresponding template (| is the cursor (or "point" in Emacs parlance)):
my $self = shift;
my (|) = @_;
This is obviously something that needs to be done all. the. time.
In this case I also use it when there are no method arguments, because it turns out it's way less hassle to type argsm<Tab>
and delete the current line than to type my $self = shift;
.
Snippets are a very useful mechanism for reducing the friction of doing common, slightly fiddly things. Reducing friction is important, because it allows you to keep your mind on what you actually want to accomplish, rather than the rote typing of syntax (and shifty characters like ()$@_
).
yasnippet
is a good snippets package for Emacs; I'm sure your editor has a similar feature/plugin available.
Yasnippets
Here's the actual snippet file snippets/perl-mode/argsm
:
#name : methods args -- my $self = shift; my () = @_;
# --
my \$self = shift;
my ($0) = @_;
As part of the snippet expansion, you can hit <tab>
to move between specific places (tab stops, like $1
, $2
, etc.) in the snippet, usually to type custom text there. $0
is the final tab stop where the expansion process ends and you can continue editing.
The tab stop text can also have a default value which can be a string, or come from calling a function, or be mirrored from what you type elsewhere in the snippet.
This is all quite powerful, especially if you add in some simple elisp functions of your own. We'll see some of these things in action below.
There's a lot more to yasnippet
, like different trigger mechanisms, dropdown menus, completion support, etc. Start small and simple, and take it from there.
You can find more yasnippet
info and installation instructions at the end of this article.
Perl snippets
yasnippet
comes with a lot of existing little snippets for various languages and modes.
I don't use most of the canned Perl snippets however: they are mostly regular Perl keyword constructs that aren't a chore to type in the first place. Instead, here are some of the ones I've defined myself, in order of actual usage and utility:
Argument unpacking
I use these right after having typed the method skeleton (with autopair-mode
for the braces):
sub basket_total_price {
|
}
argsm
#name : methods args -- my $self = shift; my () = @_;
# --
my \$self = shift;
my ($0) = @_;
args
#name : args my () = @_;
# --
my ($0) = @_;
But, wouldn't it be more convenient to include the whole sub skeleton in the snippet, curly braces and all?
Sure, you could do that. But I find that it pays off to try to hook the automated parts into the flow only where the friction starts to increase, and leave the low friction bits alone. So you type along, and just when it gets annoying, you switch over to inserting a snippet instead.
In addition, the shorter snippet can be added when editing an already existing sub. This makes the smaller, more targeted piece of text useful in more situations.
Method/sub POD
Writing POD for method signatures (i.e. the sub name, arguments and return value) is a very good habit: spending more time thinking about what's going on is usually a good thing.
In some respects doing this is like a half-way house to TDD (ok, maybe fifth-way house), because writing things down makes you think through what the method does, what parameters it takes and what it returns, any special cases that may apply and how things can go wrong. Basically imagining how it should and could be called, while not going all the way to actually writing code to do that.
podx
Invoke this just above a sub declaration to expand into boilerplate POD text:
#name : pod above sub -- =head2 name() :
# --
=head2 ${1:`(my-yas-perl-next-sub-name)`}($2) :$3
$0
=cut
This snippet is a good demonstration of the different tabstops $1
, $2
, $3
and finally $0
. The first stop is the pre-filled-in subroutine name, and when I hit <tab>
the point jumps to the next place where I can continue to enter the parameters, etc.
The default value for the sub name is where things gets interesting. The sub name comes from calling the elisp function my-yas-perl-next-sub-name
, which in this case is a simple regex match:
(defun my-yas-perl-next-sub-name ()
(interactive)
(save-excursion
(if (search-forward-regexp "sub +\\([a-z0-9_]+\\)" nil t)
(match-string 1)
"")))
So having this:
podx|
sub get_workflow_type_row {
turns into this:
=head2 |get_workflow_type_row() :
=cut
sub get_workflow_type_row {
and finally this, after documenting the method:
=head2 get_workflow_type_row($workflow_type) : $workflow_type_row | undef
Return a $workflow_type_row for the $workflow_type
(e.g. "multi_role"), or undef if it doesn't exist (either because
it's invalid or because the company has no Workflows with that
$workflow_type).
=cut
Moo/Moose lazy attribute
These are basic Moo/Moose attributes with a lazy builder, easily the most common style of attribute in our code. And when it's not, the inserted snippet text is easy to adjust.
Using the trigger words haslm
(Moo) and hasls
(Moose) for this also has the benefit of getting the syntax right, since that is annoyingly different and I can never remember which is which.
haslm
#name : has => () Moo attribute with lazy builder
# --
has ${1:name} => ( is => "lazy" );
sub _build_$1 {
my $self = shift;
$0
}
hasls
#name : has => () Moose attribute with lazy builder
# --
has ${1:name} => ( is => "ro", lazy_build => 1 );
sub _build_$1 {
my $self = shift;
$0
}
If you look at the part where it says ${1:name}
you'll see another cool yasnippet
feature: mirroring. While we type the attribute name, the builder sub name is updated in real time:
has csv_writer| => ( is => "lazy" );
sub _build_csv_writer {
my $self = shift;
}
Other Moo/Moose attributes
For completeness, here are some other less commonly used attribute declarations.
hasr
#name : has => () Moose/Moo attribute with required => 1
# --
has ${1:name} => ( is => "ro", required => 1 );
has
#name : has => () Simple Moose/Moo attribute
# --
has ${1:name} => ( is => "ro" );
hasbm
#name : has => () Moo attribute with lazy "builder" subref
# --
has ${1:name} => ( is => "lazy", builder => sub { $0 } );
POD fragments
Not as commonly used, but still somewhat useful: POD =head1
sections.
podn
#name : name with current package -- =head1 NAME
# --
=head1 NAME
${1:`(ps/current-package-name)`} -- $0
=head1 DESCRIPTION
=cut
This one is interesting in that it reuses the elisp function from Devel::PerlySense -- Copy Package Name to get the string with the current package name, which is what we want for the =head1 NAME
POD section.
Let's stop typing things that are already known!
poda
#name : pod =head1 ATTRIBUTES
# --
=head1 ATTRIBUTES
$0
=cut
podm
#name : pod =head1 METHODS
# --
=head1 METHODS
$0
=cut
podcm
#name : pod =head1 CLASS METHODS
# --
=head1 CLASS METHODS
$0
=cut
pods
#name : pod =head1 SUBROUTINES
# --
=head1 SUBROUTINES
$0
=cut
Start using yasnippet
Read more about yasnippet
here:
Install it from ELPA.
__END__
That's it!
After reading this I'm sure you've got some great ideas for snippets of your own, or maybe you already have an existing library of useful snippets?
Please share in the comments! (or over at reddit.com/r/perl)
Posted on June 12, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.