Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

Closures and sort

by traveler (Parson)
on Aug 04, 2003 at 20:24 UTC ( #280790=perlquestion: print w/replies, xml ) Need Help??

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

I have this code:
use strict; my @list = qw/ box cow dog apple ant/; # A "normal" use of sort my @sorted = sort {$a cmp $b} @list; print join (',', @sorted), "\n"; #return the sort sub and count in a list sub mcc { my $count = 0; return (sub {$count++; $_->[0] cmp $_->[1] }, $count); } # see below my @f = mcc(); # why can't I say something like mcc()[0] ??? print sort {$f[0]} @list;
It looks to me as though the anon sub returns the result of cmp and only the result of cmp, but perl says Sort subroutine didn't return a numeric value at sort1.pl line 19.

What am I missing? Also, how can I avoid using @f?

--traveler

Replies are listed 'Best First'.
Re: Closures and sort
by larsen (Parson) on Aug 04, 2003 at 20:49 UTC
    I'd go with that:
    use strict; my @list = qw/ box cow dog apple ant/; { my $count = 0; sub get_count { return $count } # an inspector as a closure # and another closure sub compare { $count++; return $_[0] cmp $_[1]; } } print sort { compare( $a, $b ) } @list; print "\n"; print get_count();
      Hmmm, it seems as though this solution does not make count unique to each sort. Thus $count contains a cumulative count as opposed to a count for each sort. I guess I should have stated my goals at the beginning.

      --traveler

        The obvious answer would be to add a method reset_count() in the block, but you would have to explicitly call it before sorting, which is prone to errors.

        Another way is to put together a state and a behaviour, building an object.

        But if you want to go on in a functional way, you may want to build a composition of a behaviour and a state, i.e. a closure. Here a short demonstration:
        use strict; my @list1 = qw/ box cow dog apple ant/; my @list2 = qw/ ant apple box cow dog/; sub make_a_profilable_sorter { my $criterion = shift; return sub { my $counter = 0; my @list = @_; @list = sort { $counter++; $criterion->( $a, $b ) } @list; return ( $counter, @list ); } } my $sorter1 = make_a_profilable_sorter( sub{ $_[0] cmp $_[1] } ); my $sorter2 = make_a_profilable_sorter( sub{ $_[0] cmp $_[1] } ); my ($count, @res) = $sorter1->( @list1 ); print "I sorted /@list1/ in $count steps producing /@res/\n"; my ($count, @res) = $sorter2->( @list2 ); print "I sorted /@list2/ in $count steps producing /@res/\n";
        In order to learn more about this subject (very interesting, in my opinion) you could refer to the following online resources:

        That's becauase this is a closure and $count retains its value between calls, since there is actually an instance of $count being carried around in the coderef.

        Either move $count into the outer scope, so you can clear it between calls, or generate a new set closures, modding the previous poster's code Re: Closures and sort :

        use strict; my @list = qw/ box cow dog apple ant/; sub mksub { my $count = 0; my $s1 = sub { return $count }; # an inspector as a closure # and another closure my $s2 = sub { $count++; return $_[0] cmp $_[1]; }; return ( $s1, $s2 ); } my ( $get_count, $compare, ); ( $get_count, $compare, ) = mksub(); print sort { $compare->( $a, $b ) } @list; print "\n"; print $get_count->(), "\n"; print "\n"; print sort { $compare->( $a, $b ) } @list; print "\n"; print $get_count->(), "\n"; print "\n"; ( $get_count, $compare, ) = mksub(); print sort { $compare->( $a, $b ) } @list; print "\n"; print $get_count->(), "\n"; print "\n";
        Which produces:
        antappleboxcowdog
        9
        
        antappleboxcowdog
        18
        
        antappleboxcowdog
        9
        
        Update: as someone else said, creating a reset_count coderef inside the same scope would do this and be cleaner than what I did.

        --Bob Niederman, http://bob-n.com
      You're right, this is much easier than returning a list. I should have thought of an inspector and a block. Thanks.

      --traveler

Re: Closures and sort
by Cine (Friar) on Aug 04, 2003 at 20:38 UTC
    You are just missing a few bits. $_->[0] should be $_[0] and then you must put $a dn $b on the param list.
    use strict; my @list = qw/ box cow dog apple ant/; # A "normal" use of sort my @sorted = sort {$a cmp $b} @list; print join (',', @sorted), "\n"; #return the sort sub and count in a list sub mcc { my $count = 0; return (sub {$count++; $_[0] cmp $_[1] }, $count); } # why can't I say something like mcc()[0] ??? print join ',',sort { &{(mcc())[0]}($a,$b) } @list;
    But you dont want to call mcc each time. That will create a new closeure each time!

    T I M T O W T D I
Re: Closures and sort
by Zaxo (Archbishop) on Aug 04, 2003 at 20:44 UTC

    The main problem is that the sort function should be written in terms of $a and $b. What you have ought to work if you call the coderef in @f as, print sort { $f[0]->($a, $b) } @list; I don't understand why you are counting calls to the comparison function after you read the counter. Does the real code have another sub clotted on $count, or do you call mcc() again to read it?

    After Compline,
    Zaxo

Re: Closures and sort
by traveler (Parson) on Aug 04, 2003 at 21:35 UTC
    I took larsen's advice but did the following. I still cannot get the count to reset before the second sort. Why doesn't calling xcmp the second time reset count?
    use strict; $|=1; my @list = qw/ box cow dog apple ant/; sub xcmp{ my $count = 0; sub get_count { return $count } # an inspector as a closure # and another closure sub compare { $count++; return $_[0] cmp $_[1]; } return(\&compare, \&get_count); } my @funcs = xcmp(); print sort { $funcs[0]( $a, $b ) } @list; print "\n"; print $funcs[1](), "\n"; @funcs = xcmp(); print sort { $funcs[0]( $a, $b ) } @list; print "\n"; print $funcs[1](), "\n";
    --traveler

      Because your sub get_count {} and sub compare {} are run once, at compile time, no matter how many times you call xcmp. So the subroutines are the same each time, even after callin xcmp in between. TO show this, do a print $funcs[1](), "\n"; after the first and second callst to xcmp() - they're the same. If you look at Re: Re: Re: Closures and sort and add a print "$compare\n"; after each call to mksubs(), you'll see these are different.

      So your functions continue to carry around the same instance of $count, but, each time xcmp is run, it is a new instance of $count which is being set to zero.

      Looked at another way,

      sub xcmp { my $count; $x = sub { return $count }; return $x; }
      returns a new coderef each time it run, each with it's own instance of count.

      Whereas

      sub xcmp { my $count; sub get_count { return $count } return &get_count; }
      returns a coderef, but it's always the same coderef, carrying aroun the same instance of $count.

      --Bob Niederman, http://bob-n.com

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others wandering the Monastery: (3)
As of 2022-09-26 13:24 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    I prefer my indexes to start at:




    Results (117 votes). Check out past polls.

    Notices?