http://www.perlmonks.org?node_id=1055925


in reply to Why doesn't Perl provide %_ as the hash equivalent of @_ in subs?

makes my code less sexy

That is such a common motivation for the use of "worst practices". I don't want sexy code. I want clear code. Ugly is often a good thing. Perl is full of ugly sigils, which is something I liked from the beginning. I find they can greatly aid clarity and speed visual parsing of code.

But the worst thing that the desire for sexy code does is motivate the use of huge piles of problematic hidden complexity just to get slightly "cleaner" syntax. That leads to source filters and similar madness. I've read Devel::Declare documentation that goes to great lengths to talk about how stupid of an idea a Perl source filter is. And then the developers implement something almost identical to a source filter (and having the same level and type of problems). And their motivation for such a stupid thing? They didn't want you to have to type one single "extra" character (a semicolon).

I urge you to get over the desire to be aroused by reading source code.

I wouldn't want %_. Unpack your arguments into lexical variables at the top of the sub. Typing $_{whatever} over and over doesn't detect when you typo the key name. Please oh please don't require people to search the entire code of your routine in order to figure out what parameters can be passed to it!

Not too long ago we spent some time standardizing argument passing for our team's Perl best practices. We standardized on passing named arguments in this manner:

whatever( { user_id => $user, campaign => $cmp } );

You'll note the inclusion of ugly brace characters, { and }. Part of the reason for that is because those tell the person reading the Perl code that the stuff inside is key/value pairs forming a real hash. For example, that makes it clear that order cannot matter. It also tells Perl that these are real name/value pairs.

We also do that because it helps avoid the temptation of extending interfaces in ambiguous ways. For example, we separate the key/value pairs for DB records from key/value pairs for optional method arguments from the tiny number of simple, required method arguments (if any):

$account->add_client( $db, # DBI handle { name => $client, status => 'new' }, # Data { require_unique => 1 }, # Options );

We explored a bunch of possible interfaces for providing a helper for unpacking such arguments. But no interface was other than trivially shorter than the below. And they were all significantly less flexible and less obvious (and each had different, worse problems).

sub add_client { my( $db, $rec, $opt, @bad ) = @_; $opt ||= {}; my $uniq = delete $opt->{require_unique} || 0; my $fatal = delete $opt->{fail_fatal} // ! $uniq; my $contract = delete $opt->{contract}; my $context = _context( delete $opt->{referrer} ); argue( \$opt, \@bad, { db => $db, rec => $rec } );

argue() just complains about unknown options, too many arguments, or (the simpler cases of) missing required arguments, filling in the sub/method name and the calling line of code for you.

Those few ugly, somewhat repetitive lines have the advantage of being ordinary, real Perl code. So they support things you might want like comments. The default values can be any Perl code (including based on other arguments). You can distinguish between || and // (and other tests). You can preprocess arguments. And all of these things are completely clear to anybody who can read Perl code. Yet most of the time we have just 1 line per option.

Yep, you can certainly consider it boilerplate. But it is clear, informative, flexible boilerplate. Ugly and so very useful.

- tye