A point illustrated in two digressive examples.
Plack is Silly Simple
I gave a presentation to the Portland Perl Mongers earlier this month, in which I explained just how simple Plack is:
- A web application is a function reference.
- That function reference takes a single parameter, a Plack::Request object.
- That function reference returns a single result, a Plack::Response object.
It's so simple, it's silly. At least it seems silly. What's the advantage over something like mod_perl or CGI or FastCGI?
Composability and middleware.
Composability
I spend part of my time consulting with a couple of friends on a project named ClubCompy. They've built a virtual 8-bit computer in JavaScript and HTML 5—you don't have to install any software besides a modern web browser to get a simple programming environment. (Obviously building more exciting programming environments is possible, but the approach is didactic, favoring simplicity and text-based programming over something like Scratch.)
ClubCompy's code takes advantage of JavaScript's advantages and tries to
avoid JavaScript's disadvantages. All of the classes in CC attach to a single
god object which is a global
variable named ClubCompy
. If you want to instantiate another
object, you access its name through a property on the ClubCompy
object. This is all well and good—it reduces contamination of the global
namespace.
Except that there's still contamination of the global namespace.
CC has a rudimentary inclusion system modeled somewhat after the C programming language's #include
system. When you include a file, you must #define
a symbol which you check as a flag to make sure you don't include a file again. In JavaScript this looks something like:
if (!ClubCompy.script_bufferedChar)
{
ClubCompy.script_bufferedChar = true;
...
}
Every included script (because you don't want tens of thousands of lines in a single file in any programming language) has a similar guard.
One obvious improvement is:
ClubCompy.scripts = {};
if (!ClubCompy.scripts.bufferedChar)
{
ClubCompy.scripts.bufferedChar = true;
...
}
... which has its obvious parallels to Perl 5's %INC
and could ggeneralize further into something more like:
ClubCompy.require( 'bufferedChar' );
... to manage this information.
The inclusion mechanism in ClubCompy wraps the contents of each included file in an anonymous function so as to protect the global namespace. That is to say, each individual file can declare its own lexical variables without worrying that those lexical variables leak out elsewhere. This is well and good.
Then Dave said "It's too bad we can't have multiple ClubCompy instances running on a page."
Remember how I said Plack is silly simple?
On Naming Anonymous Things
ClubCompy currently only lets you run one instance on a page because the
ClubCompy
object is global. You get one. That's it. There are no
more. This object is global so that everyone can refer to it by name without
worrying about how to access it.
That's why people make things Singletons, right?
If the ClubCompy
object weren't global, there'd be no problem
running multiple instances of ClubCompy on any given page, because they
wouldn't be in conflict—but how do you find a global object if
it's not global?
I belabor the point (many of you already know the answer) to explain one in
terms of the other as well as a design principle in terms of both. In
JavaScript, can you tell if ClubCompy
is a global variable in this
expression:
ClubCompy.renderFrame(frameObj);
It's easier in this snippet:
var render = function (frameObj)
{
ClubCompy.renderFrame(frameObj);
}
In truth, the JavaScript compiler doesn't really care. You could just as well:
var render = function (cc, frameObj)
{
cc.renderFrame(frameObj);
};
Or in Perl 5:
my $render = sub
{
ClubCompy->render_frame( shift );
};
... versus:
my $render = sub
{
my ($cc, $frame) = @_;
$cc->render_frame( $frame );
}
Perl doesn't care whether the invocant is a hard-coded string or a global variable or a lexical scoped outside of the block or a lexical parameter passed to the function. Likewise, JavaScript will look up the symbol according to its regular rules.
In other words, allowing ClubCompy to run multiple instances per page requires only changing the binding of the ClubCompy symbol within the libraries which use it. If the top-level code which creates a new ClubCompy instance does so without polluting the global namespace, top-level code can create multiple instances which do not conflict at the global namespace level.
The loader which wraps all of these included JavaScript files in an anonymous function turns from:
var includedFile = function ()
{
# ... included text here
};
includedFile();
... to:
var includedFile = function (cc)
{
var ClubCompy = cc;
# included text here
};
includedFile( nonGlobalClubCompy );
... and everything else just works.
Behold the magic of lexical variable encapsulation and the magic of closures—because a ClubCompy instance on a page is just a function reference.
I thought Plack was even simpler, not requiring any objects at all. A Plack web app is a function reference that takes one argument (a hash containing the environment and streams) and returns a single result (an arrayref containing the HTTP status, the HTTP headers and the response body as a string or IO stream.
The PSGI specification does not require objects. Plack provides helper request and response objects. I waffle on how to explain Plack/PSGI in the most useful and most easily understood way.