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

Dear monks, I need to consult your wisdom. Consider:

use strict; sub do_nothing { @_ } my @in = qw( e n i g m a ); my @out = sort do_nothing( @in ); print "@out\n"; __END__ % perl enigma.pl e n i g m a
To elucidate this puzzle, I consulted the inscrutably wise (though still fallible) -MO=Deparse:
% perl -MO=Deparse enigma.pl sub do_nothing { use strict 'refs'; @_; } use strict 'refs'; my(@in) = ('e', 'n', 'i', 'g', 'm', 'a'); my(@out) = (sort do_nothing @in); print "@out\n"; __DATA__ enigma.pl syntax OK
So perl is interpreting sort do_nothing( @in ) as sort do_nothing @in, which, given the explicit use of parentheses, seems to me beyond perverse.

Is this a bug or is this a feature?

the lowliest monk

Replies are listed 'Best First'.
Re: Perversity of sorts
by Tanktalus (Canon) on Apr 01, 2005 at 03:39 UTC

    Think of built-in functions as subs with prototypes on steroids (whether you parse that as "(subs with prototypes) on steroids" or "subs with (prototypes on steroids)", either one is correct).

    In this case, Perl is desperately trying to come up with a way to parse "sort do_nothing( @in )" in such a way as to match one of the two allowable formats. And it's getting it wrong. The bias seems to be for the "sort BLOCK EXPR" format over the "sort EXPR" format. Which largely makes sense from the abstract - "sort BLOCK EXPR" could be a subset of "sort EXPR", so we need to provide that bias.

    Once we've gone down that road, do_nothing is being passed no arguments, returning an empty array in scalar context results in "0", and we end up having a block that always returns 0, and thus the input to sort is passed back out, unchanged.

    With whitespace being significant in perl 6, that ambiguity goes away (to be substituted by whitespace being significant, unlike pretty much every other language).

      my @results = @foo.sort(&comparator); my @results = @foo.sort:{ $^a <=> $^b };
      What whitespace? :)
Re: Perversity of sorts
by Roy Johnson (Monsignor) on Apr 01, 2005 at 01:40 UTC
    As an additional datapoint, if you call it with a full complement of parentheses:
    my @out = sort(do_nothing(@in));
    the output is sorted, because sort knows that it has only one argument. Sort is definitely thinking that do_nothing is its comparator, and is putting it in scalar context. Here's an interesting variation:
    sub do_nothing { wantarray ? @_ : -1 }
    yields scrambled output, because it's a faulty comparison function. So if you make it
    sub do_nothing { wantarray ? @_ : ($b cmp $a) }
    you get reverse-sorted output. But I don't know the innards of perl well enough to say why it is parsing it that way, though I can kind of understand what's happening: you can say sort numerically @array and it recognizes that numerically is a function. And (@array) is really the same as @array, so that understanding still works. If you provide the function-calling sigil, it gets a clue:
    my @out = sort &do_nothing(@in);
    yields sorted output.

    Caution: Contents may have been coded under pressure.
Re: Perversity of sorts
by Mugatu (Monk) on Apr 01, 2005 at 00:54 UTC

    I am going to take a guess that given sort do_nothing(@in), do_nothing is being parsed as the name of the sort sub, and the (@in) is being parsed as the list to sort on. That's what it appears to be doing from the Deparse output, anyway.

    Update: maybe not. this code does the same thing, and it doesn't use a named sub at all:

    print sort sub { print "Args: @_\n"; @_ }->(qw(one two three))

    Another update: using a named sub, it acts differently than the anonymous sub, though it returns the same thing and the anonymous sub gets sorted, but this one doesn't:

    sub byfoo { print "Args: $a $b\n"; @_ } print sort byfoo(qw(one two three));

    So perhaps my initial guess was correct, and I got tricked by the anonymous sub. I eagerly await a response from someone who can explain this! :-)

    Final update: Ah, Tanktalus' post cleared it all up for me. How silly that I had forgotten sort can take a list and use the default comparison function! That explains why my anonymous coderef gets all the values at once, and they subsequently get sorted.

      I am going to take a guess that given sort do_nothing(@in), do_nothing is being parsed as the name of the sort sub, and the (@in) is being parsed as the list to sort on. That's what it appears to be doing from the Deparse output, anyway.

      Yes, that's exactly what Deparse shows, and that's my point: why is perl behaving this way. Perhaps the following variant makes the point more forcibly. I have redefined do_nothing to behave like a typical sort sub; the output now is sorted, but note that the sort sub (do_nothing) is being put to work with the syntax do_nothing( @in ):

      use strict; sub do_nothing { $a cmp $b } my @in = qw( e n i g m a ); my @out = sort do_nothing( @in ); print "@out\n"; __END__ % perl enigma.pl a e g i m n

      the lowliest monk

Re: Perversity of sorts
by shemp (Deacon) on Apr 01, 2005 at 00:22 UTC
    Without an explicit return, a subroutine returns the final statement evaluated, which in your case is
    @_
    If you defined do_nothing as:
    sub do_nothing {}
    Then you'd get a different result, namely there'd be nothing in @out
    Update: Sorry, didnt test this. You will actually get the error:

    Sort subroutine didn't return a numeric value at nothing.pl line X

    if you define do_nothing() as i described. But the reasoning still stands.

      I realize that; the puzzle (for me at least) is that sort does not sort the returned contents of @_. (Actually, this puzzle is "explained" by -MO=Deparse's output; now it is the perversity of this output that needs explaining.)

      Something tells me that I'm missing something whoppingly obvious...

      the lowliest monk