Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

How to get sort-like semantics?

by b4swine (Pilgrim)
on Oct 25, 2007 at 12:21 UTC ( [id://647153]=perlquestion: print w/replies, xml ) Need Help??

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

I was trying to learn prototypes, so decided to make a reduce function as follows.
=usage reduce \&f @x; eg. reduce {$a+$b} @x; # sum @x reduce {$a*$b) 1..7 # factorial(7) = 7! =cut sub reduce (&@) { my $f = shift; local ($a, $b) = shift; $a=&$f while $b=shift; return $a; } my $x = reduce { $a*$b } 1..7; print $x;
It works. My question is how can I get it to have semantics exactly like sort?
Update: Since there seems to be some confusion as to what I meant by this question, let me elaborate from the documentation about sort:

If the subroutine's prototype is ($$), the elements to be compared are passed by reference in @_, as for a normal subroutine. This is slower than unprototyped subroutines, where the elements to be compared are passed into the subroutine as the package global variables $a and $b.

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.

moritz and duelafn seem to have addressed most of the issues.

Replies are listed 'Best First'.
Re: How to get sort-like semantics? (\&f)
by tye (Sage) on Oct 25, 2007 at 15:47 UTC

    If your concern is $a and $b, then see other replies.

    If your concern is the first line of your usage documentation:

    reduce \&f @x

    then you are out of luck. Part of the point of that syntax is that the function name is optional. And that is one of the "prototypes" that is available to built-ins but isn't available to user functions. Note that prototype("CORE::sort") returns undef to indicate that its calling conventions can't be expressed in a sub prototype.

    Check opcode.pl in the Perl source and you'll see that sort's argument processing is defined by "dm@ C? L". User defined functions don't have access to all of those flags. The "?" means that you can just leave off the code part, which would make no sense for a "reduce" implementation.

    If you use the typical "sub reduce(&@)" prototype, then you can always pass a code ref to your function via &reduce( \&f, ... ). You can also usually drop the & or even the parens, but I suspect that might vary between versions of Perl. See Algorithm::Loops for some comments on quirks in what types of arguments sort can deal with on different versions of Perl, since the quirks appear to be similar. But it looks like you can get away with that syntax except for leaving off the comma on all recent Perls. The lack of the comma has to do with sort making the subroutine name optional so you shouldn't emulate that part anyway.

    - tye        

Re: How to get sort-like semantics?
by moritz (Cardinal) on Oct 25, 2007 at 12:47 UTC
    The Perl Cookbook has an example of how to do this, and you can also take a look at the List::Util source, it has a reduce method as well.

    I think you have to enter the local variables $a and $b into the caller's namespace.

      so should Language::Functional


      Carter's compass: I know I'm on the right track when by deleting something, I'm adding functionality
Re: How to get sort-like semantics?
by duelafn (Parson) on Oct 25, 2007 at 12:51 UTC

    Is the problem you are having in placing this function into a module? If that is the case, then the problem is getting ahold of the caller's $a and $b. Check out the code from reduce in List::Util.

    sub reduce (&@) { my $code = shift; no strict 'refs'; return shift unless @_ > 1; use vars qw($a $b); my $caller = caller; local(*{$caller."::a"}) = \my $a; local(*{$caller."::b"}) = \my $b; $a = shift; foreach (@_) { $b = $_; $a = &{$code}(); } $a; }

    Update: D'oh, a day late and a dollar short.

    Good Day,
        Dean

      The use vars isn't necessary, since you're using a lexical $a and $b.

        I was wondering about that when I copied the code over. My conjecture was that use vars was needed for an older version of perl, but I have no guess as to why an older perl might need it or what version of perl needs it.

        Good Day,
            Dean

Re: How to get sort-like semantics?
by FunkyMonk (Chancellor) on Oct 25, 2007 at 14:22 UTC
    My question is how can I get it to have semantics exactly like sort?
    Do you mean so both of the following examples would work?
    my @A = mysort { lc $a cmp lc $b } @list; my @B = mysort @list;

    I don't think you can, as the mandatory arguments must occur before the optional.

    Also note that sort is missing from the list of examples in perlsub. grep is listed, but grep's code block is mandatory.

Re: How to get sort-like semantics?
by jdporter (Paladin) on Oct 25, 2007 at 12:38 UTC
    sub mysort(&@) { my $func = shift; sort $func @_ }

    What exactly do you mean by "sort-like semantics"?

    A word spoken in Mind will reach its own level, in the objective world, by its own weight
      What do you mean by "mean"?
Re: How to get sort-like semantics?
by ikegami (Patriarch) on Oct 25, 2007 at 19:47 UTC

    Are you looking how to do "If the subroutine's prototype is ($$)"? prototype.

    use strict; use warnings; sub reduce(&@) { my $f = shift; my $a = shift; if (prototype($f)||'' eq '$$') { $a = $f->($a, shift) while @_; } else { my $b; no strict 'refs'; my $caller = caller(); local *{$caller."::a"} = \$a; local *{$caller."::b"} = \$b; $b = shift, $a = $f->() while @_; } return $a; } sub prototyped($$) { $_[0] * $_[1] } sub unprototyped { $a * $b } $\ = "\n"; print reduce { $a * $b } 1..6; # 720 print reduce \&prototyped, 1..6; # 720 print reduce \&unprototyped, 1..6; # 720

    Update: Adjusted to use the caller's package.

      What if we skipped checking the prototype and set up both @_, and $a and $b?

      sub reduce(&@) { my $f = shift; my $a = shift; my $b; our @args; (local *_, local *args) = (sub { \@_ }->($a, $b), \@_); no strict 'refs'; my $caller = caller(); local *{$caller."::a"} = \$a; local *{$caller."::b"} = \$b; $b = shift(@args), $a = &$f while @args; return $a; }

      Turns out it's negligeably faster for moderately long lists.

      Rate ike1_p ike1_n ike2_p ike1_i ike2_i ike2_n ike1_p 6.34/ms -- -2% -2% -4% -6% -7% ike1_n 6.46/ms 2% -- -1% -3% -4% -5% ike2_p 6.50/ms 3% 1% -- -2% -3% -4% ike1_i 6.63/ms 5% 3% 2% -- -1% -2% ike2_i 6.72/ms 6% 4% 3% 1% -- -1% ike2_n 6.79/ms 7% 5% 4% 2% 1% -- (I)nline: reduce { $a * $b } 1..100; (P)rototype: reduce \&prototyped, 1..100; (N)o prototype: reduce \&unprototyped, 1..100;

      Full Results

      Benchmark code

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://647153]
Approved by Corion
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others contemplating the Monastery: (4)
As of 2024-04-24 19:16 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found