Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

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

by smls (Friar)
on Sep 26, 2013 at 09:47 UTC ( [id://1055803]=perlmeditation: print w/replies, xml ) Need Help??

TL;DR version:

I think %_ should be available inside subroutines, and contain the same parameter list as @_ but interpreted as a hash.
Why is this currently not the case?

Longer version:

For functions that needs to handle a flexible number of named arguments, one would traditionally pass a hash and then add a line at the top of the function to rebuild the hash from @_:

sub dostuff { my %arg = @_; # Access named parameters like $arg{foo} } dostuff( foo => 'FOO', bar => 1 );

If it's a long and unique function, that's fine because the syntactical overhead is negligible. However, consider cases where you have multiple small (say, one-liner) functions that are all called the same way - for example when using lambda functions as callbacks:

sub_with_callbacks ( red => sub { my %arg = @_; .. $arg{foo} .. }, green => sub { my %arg = @_; .. $arg{foo} .. }, blue => sub { my %arg = @_; .. $arg{foo} .. }, yellow => sub { my %arg = @_; .. $arg{foo} .. } );

The my %arg = @_; has now become annoying boilerplate that makes my code less sexy. I hate that.

Compared to other languages, Perl usually excels at providing little tricks and magic variables and whatnot to allow me to create cute and condensed snippets of code in cases where that is appropriate. But here, the (to my mind) obvious feature is missing: Allowing direct access to the parameter list (interpreted as a hash) via %_ - which would make it possible to replace the previous example code block with the following, much nicer one:

sub_with_callbacks ( red => sub { .. $_{foo} .. }, green => sub { .. $_{foo} .. }, blue => sub { .. $_{foo} .. }, yellow => sub { .. $_{foo} .. } );

I wonder why Perl does not provide this feature, seeing as:

  • It fits semantically: $_[0] accesses positional parameters, $_{foo} would access named parameters.
  • To me, it just feels like the obvious thing to do with %_. Surely I can't be the only one?
  • %_ already counts as a reserved variable name (despite not being used), so it wouldn't break any backwards compatibility to add it.
  • As for performance impact, I'm sure the devs could make it so %_ is only created for functions that actually use it, and/or implement it as a tied hash or some other magic.

I'd be interested in hearing any qw(thoughts opinions explanations background-info musings) that my fellow Monastery dwellers might have on this.

Replies are listed 'Best First'.
Re: Why doesn't Perl provide %_ as the hash equivalent of @_ in subs?
by ikegami (Patriarch) on Sep 26, 2013 at 14:12 UTC

    It would be silly to always create a hash from the arguments.

    • Extremely expensive where it's not needed.
    • Causes warnings if you pass undef at even indexes.
    • Causes warning if you pass an odd number of arguments.
    • Useless for methods.
    • Useless for sub that have any positional parameters.

    So either create it yourself (local %_ = @_; or alias local %_ = @_;) when it's acceptable, or create an attribute you can specify on a sub which does it for you (e.g. sub foo :named_args { ... }).

Re: Why doesn't Perl provide %_ as the hash equivalent of @_ in subs?
by choroba (Cardinal) on Sep 26, 2013 at 09:58 UTC
    Nice idea, really. I am just not sure how to make it work with objects: the first parameter is a class or an object reference, the named parameters follow.
    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

      Hm, good point.
      I guess it could be made so that in case of an uneven sized list, it excludes the first item.

      But that would break consistency with regard how uneven lists are usually interpreted as hashes, which I think is to exclude the last item and printing a warning.

        Or, maybe, the hash is created once you use the variable for the first time, so you can safely shift the $self or $class.
        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
Re: Why doesn't Perl provide %_ as the hash equivalent of @_ in subs?
by moritz (Cardinal) on Sep 26, 2013 at 15:10 UTC

    There are at least four commonly used calling conventions for named arguments in existing Perl 5 code:

    yoursub(a => 1); yoursub({ a => 1 }); $obj->meth(a => 1); $obj->meth({a => 1});

    In addition, people like to mix positional arguments with named arguments, for example from AnyEvent::HTTP:

    http_get $url, timeout => 20, sub { # callback here };

    No, introducing %_ doesn't even begin to cover the use cases that are out in the wild, and it's another legacy feature that needs to be supported when/if proper routine signatures are introduced.

    And that is the thing that should be done. There are several promising modules on CPAN that provide proper routine signatures, most of which converge on the syntax that Perl 6 uses. Getting one of them rock solid and into core Perl 5 would be a much better solution. (And I believe people are working on it already).

Re: Why doesn't Perl provide %_ as the hash equivalent of @_ in subs? (ugly++)
by tye (Sage) on Sep 27, 2013 at 04:19 UTC
    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        

      "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!"

      Yes, it wouldn't make sense to use %_ in most long & complicated routines.

      I never intended to suggest that it would become The One True WayTM for accessing subroutine arguments.

      I just think it would be a useful additional tool in the Perl developer's toolbox, nicely complementing the existing ones (in particular @_ which it would semantically mirror very closely), to be deployed with discretion.

      I myself would use %_ sparingly, just like I currently use direct access to $_[0] (or, similarly, $_ in foreach loops) sparingly. But sparingly does not mean "never useful"!
      Remember that Perl is also popular for shell one-liners & small system administration/convenience scripts etc., not just for huge professional web applications. And not every subroutine is so big and complicated, that direct usage of @_ or %_ would get lost in the noise or make it impossible to predict unwanted side-effects.

      Example:

      In the OP I've already given a use-case that can appear even in big Perl applications, where I think %_ has its place - let me elaborate on that:
      When you have, say, a module which offers functionality for which it takes a whole set of related callback functions, which when called should get access to a set of variables (but as the module author you can't predict which variables will actually be used by which user callback), then passing a hash makes sense. Now if, in the user code, the callbacks turn out to be very simple and short (say, <1 line each), then copy&pasting the same boilerplate between all of them for unpacking the parameters into a hash just adds noise - "less sexy" in this case also means "less readable".

      Precedent:

      Besides @_, the use of %_ as I imagine it can be compared to the use of $_ in foreach loops.
      The "safe", "clean", "text-book" way is to declare an explicit loop variable, and in most cases that does indeed makes sense:

      foreach my $person (@people) { ## ... ## Long and complex code that uses $person many times, might ## potentially be refactored into nested loops in the future, etc. ## ... }

      Using $_ instead of $person in that example would entail many of the same or similar disadvantages/problems as the ones that you and other commenters have invoked against the use of %_ in subs.

      And yet, Perl does make $_ available as a built-in language feature.
      And I, for one, happily use it to make code shorter and cleaner in those cases where it doesn't hurt, e.g.:

      say $_->{name} foreach @people;
      $age_sum += $_->{age} foreach @people;

      Perl trusts me to make an informed decision when to use $_ and when not. The fact that it can be used in ways that make code unreadable or buggy, wasn't deemed enough of a reason to exclude it from the language.

      Why not apply the same principle to %_?

        Why not apply the same principle to %_?

        Because $_ costs (almost) nothing to provide; so people not using it do not pay a penalty for those that do.

        Setting up a hash from an arbitrary list of value would require substantial extra validation to see if doing so made any sense -- are there an even number; does the first of each pair make sense as a key etc. -- then building the hash and aliasing the hash values to the input arguments etc.

        That is a substantial penalty for everyone who doesn't used that hash to pay, for the convenience of the few that would.

        If you want that facility, do it yourself, cos I have no use for it and I don't want to pay the penalty.


        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Why doesn't Perl provide %_ as the hash equivalent of @_ in subs?
by tobyink (Canon) on Sep 26, 2013 at 21:36 UTC

    I've been working on a Method::Signatures-like module recently, and it does use %_ for this purpose, but only if the sub is known to take at least one named parameter.

    If you're interested in beta testing, I can let you know if/when I have something vaguely usable.

    use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name
Re: Why doesn't Perl provide %_ as the hash equivalent of @_ in subs?
by vsespb (Chaplain) on Sep 26, 2013 at 10:00 UTC
    Another problem is aliasing. What should happen if you modify %_ keys ? %_ values?

      Or if you modify @_, for that matter. Will we now have 2 copies of all the arguments with all the memory headaches that brings or will it be a reference (and how would that work)?

      For all the nice-to-haves such a feature might provide I think there are too many drawbacks and quite frankly the single-line "boilerplate" to achieve the same is no great hardship.

        "or will it be a reference (and how would that work)?"

        I don't know enough about how Perl implements arrays and hashes and about how tied hashes work, to answer that.

        But even if there's no good way to meaningfully allow modification of %_, that doesn't mean the magic variable should not be provided at all, it just means it should be read-only.

Re: Why doesn't Perl provide %_ as the hash equivalent of @_ in subs?
by hdb (Monsignor) on Sep 26, 2013 at 10:24 UTC

    You could be using local %_=@_; in place of my %arg=@_; which at least let's you work with $_{foo} and $_{bar} (in preparation for the time when your proposal will be a feature of Perl).

      which is never, cause thats so slooow and it would be a pragma anyway and you can createonenow with Devel::Declare

        "and you can createonenow with Devel::Declare"

        More or less all conceivable language features can probably be implemented using Devel::* modules, but that's no argument against wanting something to be a built-in Perl feature - so it can be conveniently used even in casual self-contained Perl scripts, and so it will be fast.

        Why is that slower than my %arg = @_;?

Re: Why doesn't Perl provide %_ as the hash equivalent of @_ in subs?
by Anonymous Monk on Sep 26, 2013 at 10:25 UTC

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://1055803]
Approved by vsespb
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others contemplating the Monastery: (5)
As of 2024-03-28 08:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found