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

Hello Monks,

everyone knows the basic principles of closures:

#!/usr/bin/perl -w use strict; sub compare { my @mem; return sub { my $x = shift; my $y = shift; push @mem,$y if($x eq $y); return @mem; }; }; my $code = &compare; print "IT1: ",&$code('a','b'),"\n"; print "IT2: ",&$code('b','b'),"\n"; print "IT3: ",&$code('a','c'),"\n"; print "IT4: ",&$code('c','c'),"\n";
Unfortunatedly this behaviour doesn't suit the needs I actually encounter. Much more suitable would be a subroutine, that is able to remember its state whenever called. Without the additional
referencing my $code = &compare; stuff. I.e. AS IF it used a global variable. But it isn't allowed to use global variables. Just whenever you call such a self-aware metaclosure it returns something back that can depend on the last state it had.

I tried to implement that with a closure inside a sub, but failed miserably. Anyone who could help me out here?

Thank you and

Bye
 PetaMem

Replies are listed 'Best First'.
Re: A metaclosure? (use static vars)
by demerphq (Chancellor) on May 13, 2002 at 11:10 UTC
    Well, maybe ive completely missed the point but implementing a subroutine that keeps its state just seems to be a more complex way of saying that you need static variables. And adding static variables to a subroutine is easy. Just put the subroutine inside an anonymous block, along with lexical declarations for the vars you need to be static and away you go...
    #normal sub sub foo { return join ("#",@_); } { #Anon block for static vars to live in. my $join="-"; sub bar { my $ret=join($join,@_); $join=($join eq "-") ? ":" : "-"; return $ret; } } # End static block print foo(1..10),"\n"; print foo(1..10),"\n"; print bar(1..10),"\n"; print bar(1..10),"\n"; print bar(1..10),"\n"; __END__ 1#2#3#4#5#6#7#8#9#10 1#2#3#4#5#6#7#8#9#10 1-2-3-4-5-6-7-8-9-10 1:2:3:4:5:6:7:8:9:10 1-2-3-4-5-6-7-8-9-10
    Hope thats what you needed...

    :-)

    PS: If memory serves me right I believe that the anon block should become a BEGIN block under some circumstances (mod_perl maybe?). I seem to recall something by tye on the matter but I dont recall the details, and I cant find the post in question.

    Yves / DeMerphq
    ---
    Writing a good benchmark isnt as easy as it might look.

      There are lots of cases where the BEGIN fixes problems. mod_perl is one. Several others boil down to variations on this problem:

      #!/usr/bin/perl -w use strict; print "Starts out false: ", boolToggle(), $/; print "Then is true: ", boolToggle(), $/; print "Then false: ", boolToggle(), $/; print "Then true: ", boolToggle(), $/; # BEGIN # Remove first "#" from this line to fix bug. { my $static= 1; sub boolToggle { $static= $static ? 0 : 1; return $static; } } print "Then opposite of last time: ", boolToggle(), $/;
      which produces:
      Starts out false: 1 Then is true: 0 Then false: 1 Then true: 0 Then opposite of last time: 0
      Trying to topologically sort your subroutine declarations to avoid this problem is probably not a good solution.

              - tye (but my friends call me "Tye")
Re: A metaclosure? Howto?
by Biker (Priest) on May 13, 2002 at 10:25 UTC

    I'd solve this using a class. The object instantiated would be initialized with a given value by the new() method (or by a default value) and then the value would be manipulated with a method, that would apply the required changes to the value and return the updated value.

    I would also ask myself the question if I should implement this as a Singleton class, all depending upon the needs at hand.


    Everything went worng, just as foreseen.

Re: A metaclosure? Howto?
by broquaint (Abbot) on May 13, 2002 at 10:33 UTC
    Sounds like you're after a coroutine, which unfortunately is not natively supported in perl. You might want to check out the Coro module which implements them with some success, or wait around for Perl6 ;-)

    Of course there's always TIMTOWTDI and you could try something like this ...

    { my $lex_sub; sub coro { my @lexvar = @_; $lex_sub = sub { print pop @lexvar, $/; return scalar @lexvar; } if not defined $lex_sub; $lex_sub->(); } } my @words = qw(foo bar baz quux); coro() while coro(@words); __output__ quux baz bar foo
    But that is hackish, ugly and not much of a coroutine. I'd say stick with Coro or check out ruby for your coroutine fix.
    HTH

    _________
    broquaint

Re: A metaclosure? Howto?
by educated_foo (Vicar) on May 13, 2002 at 13:38 UTC
    Or if you want to generate more than one of these things:
    #!/usr/bin/env perl use strict; sub precompare { my $name = shift; my ($caller) = caller; $name = "$caller\::$name"; my @mem; no strict 'refs'; if (*{$name}{CODE}) { warn "Redefining sub $name\n"; } *{$name} = sub { my $x = shift; my $y = shift; push @mem,$y if($x eq $y); return @mem; }; }; precompare 'compare';
    /s
      Hmm. While the idea is interesting the implementation scares the willies out of me. Lets hope that a real version would check to make sure that $name is of a legal value. Imagine what would happen if $name was 'precompare' for a simple example. Or open()....

      Yves / DeMerphq
      ---
      Writing a good benchmark isnt as easy as it might look.

        I suppose it could refuse to redefine a sub that already exists, instead of just complaining. On the other hand, "package::open" wouldn't trounce "CORE::open", it would just lead to unexpected behavior (or maybe a warning). And if you redefined "precompare", you wouldn't be able to use it any more. But then again, if you did this, you probably wouldn't deserve to use "precompare" in the first place... Perl has a lot of chainsaws, and it's great fun to use them with reckless abandon.

        /s