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

I was playing with sorting using a code reference, and found that I cannot obtain the code reference through a subroutine call without first storing the reference in a scalar.

EDIT 20060726 12:14 - Adding a summary at the top as I did not express my question clearly
It is permissible to sort using a scalar which holds a reference to a function, but it is an error to skip the step that stores the reference in a scalar. I wanted to know the distinction between the two. Good:

my $sorter = \&some_function; sort $sorter (1,2,3);
Bad:
sort \&some_function (1,2,3);
It is also an error to obtain the coderef from a factory, without first storing the coderef in a scalar.
sort make_sorter() (1,2,3)
(Where make_sorter is a function that returns a coderef. The referenced sub being a valid sort function.)
End edit

Original example:

This code works as expected:

use strict; use warnings; my $sorter = get_numeric(); my @sorted2 = sort $sorter (3,2,1); print join(', ', @sorted2); sub get_numeric { return \&numeric; } sub numeric($$) { my ($a,$b) = @_; $a <=> $b; }
This code does not work at all:
use strict; use warnings; my @sorted2 = sort get_numeric() (3,2,1); print join(', ', @sorted2); sub get_numeric { return \&numeric; } sub numeric($$) { my ($a,$b) = @_; $a <=> $b; }
The error is: syntax error at pmsort line 4, near ") (" Is there a clean way to do this, or is the temporary storage in a scalar required?

Environment is perl 5.8.2 on openbsd.

Replies are listed 'Best First'.
Re: Is it possible to sort using a coderef, without first storing the coderef in a scalar
by davido (Cardinal) on Jul 26, 2006 at 04:48 UTC

    It is legal to use a coderef with sort, but if you use a named sub, that sub is expected to return -1, 0, or 1. If it returns anything non-numeric such as a coderef, you get a fatal.

    Your first example calls get_numeric() once, assigning the coderef it returns to $sorter. $sorter, containing a coderef, has its referant code executed by sort. But get_numeric() is only called once, before the sort routine is ever invoked. This is an important distinction; $sorter contains a coderef, that when executed returns -1, 0, or 1.

    In your second example, you should actually write it like this:

    my @sorted2 = sort get_numeric (3,2,1);

    ...because that's syntactically correct. sort doesn't want "get_numeric()", it wants subname, which is "get_numeric" (without the parens). But when you compose it as I've demonstrated above, you get a different error altogether. You get "Subroutine didn't return a numeric value..." Why? Because get_numeric() doesn't return a numeric value... instead it returns a coderef pointing to numeric(). That's an additional level of indirection that sort isn't equipped to dive into once it sees that it's been given a subname.


    Dave

      The part that I'm not quite sure about is the distinction between these two:
      # Doesn't work sort \&numeric @list; # Works my $sorter = \&numeric; sort $sorter @list;
      I know that the first example would work if I used numeric instead of \&numeric - but why does \&numeric not also work?

      I'm playing with a module that defines a set of common sort patterns, and provides methods that return coderefs that implement the pattern that was requested.

      For example, sorting a list of hashes based on the value of a user specified key. The custom implementation would be something like:

      sub sort_hashkey($$) { my ($a,$b) = @_; $a->{foo} <=> $b->{foo}; } my @sorted = sort_hashkey @list;
      My module provides this:
      my $sorter = sorthash_numeric('foo'); my @sorted = sort $sorter @list;
      It would be nice, but not neccesary, to do this:
      my @sorted = sort sorthash_alpha('foo') @list;

        If you wish to use that syntax, you could use a wrapper.

        sub mysort { my $sorter = shift; return sort $sorter @_; } my @sorted = mysort sorthash_alpha('foo'), @list;

        Because you have a choice according to the docs for sort. You can use a block, a subname, or (from the docs): "SUBNAME may be a scalar variable name (unsubscripted), in which case the value provides the name of (or a reference to) the actual subroutine to use." There is nothing in the docs that says you can use a subroutine call to return a sub reference. You can use a block, a subname, or a scalar variable containing the name of a sub or a reference to a sub. Nothing else. And your sub named in 'subname' must return an integer (-1, 0 or 1, for example). Likewise, the sub referred to in $subname must return an integer.


        Dave

Re: Is it possible to sort using a coderef, without first storing the coderef in a scalar
by ikegami (Patriarch) on Jul 26, 2006 at 05:14 UTC

    No. sort has a weird prototype. As documented, the first argument can be a block, a bareword (which is treated as a function name), or a scalar (which contains a function name or a function reference). No other expressions are not allowed, so a function call will not work.

Re: Is it possible to sort using a coderef, without first storing the coderef in a scalar
by imp (Priest) on Jul 26, 2006 at 16:50 UTC
    tye provided the answer in CB
    <tye> as to /why/ that distinction matters: it is a parser restriction (and parsing sort arguments is rather buggy anyway)
    <tye> perl -MO=Deparse -e "sort foo(), <>" says "sort foo (), <ARGV>;", which leads me to think that the space is important there
    Investigated further and this is what I found:
    perl -MO=Deparse -e 'sub a {}; my $r = \&a; sort $r (1,2)' + sub a { } my $r = \&a; sort $r 1, 2;
    perl -MO=Deparse -e 'sub a {}; sort \&a (1,2)' sub a { } sort \(&a(1, 2));