Reading code - Plack::Builder
by:
2 minutes
383 Words
2014-04-11 20:00 -0400
- Part 1 - An Overview
- Part 2 - plackup Architecture
- Part 3 - PSGI Application Architecture
- Part 4 - Plack::Builder
Plack::Builder provides a domain specific language (DSL) for middleware developers. It looks like this:
use Plack::Builder;
my $app1 = sub { ... };
my $app2 = sub { ... };
builder {
enable "Deflater";
enable "Session", store => "File";
enable "Debug", panels => [ qw(DBITrace Memory Timer) ];
mount "/narwhale" => $app1;
mount "/unicorn" => $app2;
};
How does it work? With three artful tricks.
Artful trick #1
The first artful trick is the builder
block.
sub builder(&) {
my $block = shift;
...
}
The &
is a function prototype. Perl offers some limited compile time checking
for parameters passed to subs. Here is what perldoc perlsub
says about &
:
An “&” requires an anonymous subroutine, which, if passed as the first argument, does not require the “sub” keyword or a subsequent comma.
So if I try to pass builder()
a scalar or an array or anything thats not an
anonymous subroutine, I will get a compile time error. But if I pass it an
anonymous subroutine, the compiler will allow things to continue.
Artful trick #2
The next artful trick is that Plack::Builder implements the DSL keywords as subs and then exports those subs.
package Plack::Builder;
use strict;
use parent qw( Exporter );
our @EXPORT = qw( builder enable enable_if mount );
...
sub enable {...}
sub enable_if {...}
sub mount {...}
# etc
Actually thats 90% of the whole thing isn’t it? Now its starting to look obvious. But lets continue.
Artful trick #3
There is one more interesting idea here. Notice that if I use enable
,
enable_if
, or mount
outside of a builder
block I will get an
error. This works because the DSL keywords are subs which run code references.
By default those code references refer to code which croaks an error. But when
builder
runs, those references are temporarily replaced with real working
code.
Here’s some simplified code to illustrate how it works.
our $_enable = sub { Carp::croak(...) }; # << default code reference
sub enable { $_enable->(@_) }
sub builder(&) {
my $block = shift;
...
local $_enable = sub {...}; # << temporarily assign real working code
...
my $app = $block->();
...
}