tlm has asked for the wisdom of the Perl Monks concerning the following question:

Here's another question borne out of idle curiosity: what is the most efficient way to force a list context? I used to think that it'd be assigning to the empty list, but that's not the case, at least not always, as the results below show.

First, here's the set-up:

use Benchmark 'cmpthese'; sub gr { # Guttman-Rosler transform map { substr( $_, 3 ) } sort map { sprintf( '%03d', /abc(\d+)xyz/ ) . $_ } @_; } srand 0; my @u = map 'abc' . int( rand 1000 ) . 'xyz', 1..500; cmpthese( -1, { s_aa => sub { my @s = sort @u }, s_fi => sub { ( sort @u )[ 0 ] }, s_na => sub { () = sort @u } } ); cmpthese( -1, { g_aa => sub { my @s = gr @u }, g_fi => sub { ( gr @u )[ 0 ] }, g_na => sub { () = gr @u } } );
Two sets of benchmarks, corresponding to various ways of forcing list contexts on calls to two different functions: Perl's built-in lexicographic sort function (these are the s_* items), and gr, a simple implementation of a Guttman-Rosler sort (this is basically the subroutine guttros posted by friedo here; these are the g_* items). (The results will show that either set of benchmarks would have been sufficient illustration, but it's nice to have the extra datapoint by way of crosscheck.)

And here are the results:

Rate s_aa s_na s_fi s_aa 1037/s -- -49% -49% s_na 2017/s 95% -- -1% s_fi 2036/s 96% 1% -- Rate g_na g_aa g_fi g_na 237/s -- 0% -14% g_aa 237/s 0% -- -14% g_fi 275/s 16% 16% --
There you have it: fetching the first element unequivocally beats assigning to (). I find this very surprising. After all, fetching the first element requires some honest work, but assigning to a null list is something so nominal that I would have expected it to be pretty much the list counterpart of scalar.

The approaches above are what I could think of as likely candidates for the lowest-overhead list-forcing method. But the results tell me that my intuition on these matters is a poor guide. Are there more efficient methods?

More to the point, why isn't there a direct way to just tell perl we want a list context, just like we can do with scalar context? It's silly to have trick perl into it.

the lowliest monk

Replies are listed 'Best First'.
Re: What's most efficient way to get list context? (honest)
by tye (Sage) on Apr 15, 2005 at 05:23 UTC

    I don't think I could care less which is more efficient, but I chuckled when I read your "honest work" note and so thought I should mention that the return value from () = ... (in a scalar context) is the count of items in the right-hand side list that gets thrown away. So instead of just fetching the first one, you have to count all of them. I kinda doubt that actually has much to do with your benchmark results.

    Regarding your last paragraph: What is the point of saying "I want a list context" if you don't actually want something from the list? Any use other than benchmarks? Because I'm not at all sad that Perl lacks a keyword that would have no use other than benchmarking. If you want a list, then you do something with the list and you doing that causes the list context.

    The scalar keyword makes sense because this makes sense:

    my @list = scalar function();
    it puts a single item into @list even if function() would return lots of scalars if given a list context. The equivalent:
    my $scalar = list function();

    doesn't make sense because there is no universal "list in scalar context" behavior. You have to specify which conversion (which has the side effect of inducing the context).

    Sure, if what you really want is:

    0; # void context below! list function(); # fake list context 0; # void context above!

    then there doesn't really need to be any conversion. But, again, this isn't "productive" -- it is something you only do as a hack for unusual situations. So I don't miss having some official feature just for such trickery.

    Note that you can put "return 0;" or just "0;" on the end of your benchmark subs in case you want to ensure you are getting void context (or use the source, of Benchmark.pm).

    It's silly to have trick perl into it.

    But what you are doing is a trick. So being required to write tricky code in order to do something this tricky is not silly at all. (:

    - tye        

      What is the point of saying "I want a list context" if you don't actually want something from the list?...

      The equivalent:

      my $scalar = list function();

      doesn't make sense because there is no universal "list in scalar context" behavior.

      I find these remarks puzzling. As I understand it, the standard "list in scalar context" behavior is "size of the list". Furthermore, any function whose return value changes depending on the context (e.g. by using wantarray), gives rise to situations in which this hypothetical list operator would be useful. The canonical example of this is m//. If I want to find out how many times m/(pattern)/g matched, I have to resort to mysterious-looking trickery:

      my $n = () = $string =~ m/(pattern)/g;
      or else pointless, Javaoid verbositudinositiness:
      my @useless = $string =~ m/(pattern)/g; my $n = @useless;
      It seems to me more civilized to simply request m//'s list context behavior:
      my $n = list $string =~ m/(pattern)/g;

      the lowliest monk

        As I understand it, the standard "list in scalar context" behavior is "size of the list".

        No, no. The incorrect assumption you are supposed to make at this point is that a "list in scalar context" returns "the last item" (by default). But both assumptions are wrong. Each "operation that would return a list of scalars if called in a list context" makes its own, customized decision about what it will return in a scalar context and those two are just the most common choices (and "last item" is even more common than "number of items").

        There is no default choice. @x returns the size of the array. ( $x, $y, z() ) returns the value of z() (called from a scalar context). %h returns the bucket usage. Something I can't remember returns the first item. sort returns nothing useful (and does nothing useful to boot).

        Now a less "idiomatic" way to getting the size of an arbitrary list might be worth having. You didn't express it this way because of your wrong assumption about this being "the default" list-to-scalar conversion. So I'd rename the keyword "count" or such instead of "list", at which point it makes sense.

        - tye        

        As I understand it, the standard "list in scalar context" behavior is "size of the list".
        You should note that your winner does not exhibit that behavior.
        grep 1, @list;
        would return a count (or the whole list, depending on context), but I suspect assigning to the empty list is more efficient. It's certainly a tad shorter to type.

        Update: Had grep 0, due to scrambled thinking that it would still return a count of all elements, yet not build a list. Gah. Thanks, Pustular.


        Caution: Contents may have been coded under pressure.