Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

subroutine concatenation

by imcsk8 (Pilgrim)
on Feb 09, 2005 at 21:25 UTC ( [id://429514]=perlquestion: print w/replies, xml ) Need Help??

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

Hello monks,

I'm trying to dynamically add subroutines to a program and i thought of concatenating them using refences to soubroutines.
At first it seemed like an easy task just concatenate the subroutine references to the coderef. But it is not, i get an error.
this is what i tried (among other things):
use strict; my $coderef = undef; $coderef = sub {print "SUB 1\n";}; $coderef .= sub {print "SUB 2\n";}; $coderef->();
and this is what i get:
Can't use string ("CODE(0x8161454)CODE(0x8161514)") as a subroutine re +f while "strict refs" in use at /home/ichavero/perlStuff/prueba_coder +ef.pl line 9.
my question is: is there a way of concatenating subroutine references to be called from one variable??


ignorance, the plague is everywhere
--guttermouth

Replies are listed 'Best First'.
Re: subroutine concatenation
by phaylon (Curate) on Feb 09, 2005 at 21:29 UTC
    Why don't you just save them in an Array and walk through it?
      Thanks!! i just did that
      it ended up like this:
      use strict; my @codesref = undef; $codesref[0] = sub {print "SUB 1\n";}; $codesref[1] = sub {print "SUB 2\n";}; foreach my $code (@codesref){ $code->(); }
      and it worked just fine


      ignorance, the plague is everywhere
      --guttermouth

        More idiomatic and a bit less syntax:

        my @codesref; push @codesref, sub { print "SUB 1\n" }; push @codesref, sub { print "SUB 2\n" }; $_->() for @codesref;
Re: subroutine concatenation
by revdiablo (Prior) on Feb 09, 2005 at 21:31 UTC

    Concatenation is for strings. You need to make a copy of the first coderef and make a new coderef that calls the copy. Note that the copy stays around, even though it's gone out of scope, because the new one creates a closure around it. Here's an example:

    my $code = sub { print "Sub 1"; }; $code = do { my $oldcode = $code; sub { $oldcode->(); print "Sub 2"; }; }; $code->();

    Update: this do block could be further generalized into a subroutine, but that's an exercise left to the reader. :-)

    Update: phaylon's solution is much simpler, and the OP seems happy with it, but the closure technique I've shown is a way to get both [or as many as necessary] to execute from a single starting point, [update] and without a loop at all :-P

      Sub, schmub. Overload is your (evil) friend . . .

      Update: Of course for efficiency you might want to check if UNIVERSAL::isa( $other, "CatCode" ) and pull out its coderef from $other->{_code}.

      Update: Another tweak. To paraphrase Homer Simpson, "In America first joo geet dee syntactic shoooogar, dehn joo geet dee powah, dehn joo geet de wimin."

      Update: And even more silliness, buscc that reverses the call order (new code called first then the old code).

      #!/usr/bin/perl BEGIN { ## This would really be in CatCode.pm . . . package CatCode; use UNIVERSAL qw( isa ); use overload "." => "_cat_sub", "&{}" => "_call_sub"; require Exporter; @CatCode::ISA = qw( Exporter ); @CatCode::EXPORT_OK = qw( ccsub ); sub ccsub (&) { return CatCode->new( shift() ); } sub new { my $class = shift; my $code = shift; return bless { _code => $code }, $class; } sub _cat_sub { my( $self, $other ) = @_; my $oldcode = $self->{_code}; if( isa( $other, "CatCode::Reverse" ) ) { $other = $other->{_code}; $self->{_code} = sub { $other->( @_ ); $oldcode->( @_ ); }; } elsif( isa( $other, "CatCode" ) ) { $other = $other->{_code}; $self->{_code} = sub { $oldcode->( @_ ); $other->( @_ ) }; } else { $self->{_code} = sub { $oldcode->( @_ ); $other->( @_ ) }; } return $self; } sub _call_sub { my $self = shift; sub { $self->{_code}->( @_ ) } } package CatCode::Reverse; @CatCode::Reverse::ISA = qw( CatCode ); @CatCode::Reverse::EXPORT_OK = qw( buscc ); sub buscc (&) { CatCode::Reverse->new( shift() ); } 1; } package main; BEGIN { CatCode->import( qw( ccsub ) )} BEGIN { CatCode::Reverse->import( qw( buscc ) )} my $cc = ccsub { print "one $_[0]\n" }; $cc .= sub { print "two $_[0]\n" }; $cc .= buscc { print "three $_[0]\n" }; $cc .= sub { print "four $_[0]\n" }; $cc->( "fish" ); exit 0 __END__

        If you overrode _cat_sub in CatCode::Reverse, you could replace the conditional with polymorphism and wouldn't have to use the polymorphism-hostile direct UNIVERSAL::isa call.

      Using an array is the right solution but this works.
      my $code = sub {print "Sub 1\n"}; $code = joincoderefs( $code, sub {print "Sub 2\n"}); &$code; sub joincoderefs { my @refs = @_; return sub { $_->() for @refs }; }
      outputs:
      Sub 1 Sub 2

      --

      flounder

        Using an array is the right solution

        I agree that an array of coderefs seems to be the best solution, in this case. Especially since the OP has already responded, saying that the array works. But I still think it's useful to show other techniques -- such as you have done -- which may be more appropriate in other situations. Talking about which one is the "right solution" is a different discussion altogether.

      to execute from a single starting point.

      Just put the loop in a sub, there ya go :D
Re: subroutine concatenation
by sleepingsquirrel (Chaplain) on Feb 09, 2005 at 22:06 UTC
    Probably getting off topic here, but there are a whole host of concatenative languages from which you might discover interesting techniques to apply to your code.


    -- All code is 100% tested and functional unless otherwise noted.
Re: subroutine concatenation (B tricks)
by ysth (Canon) on Feb 10, 2005 at 02:36 UTC
    You could wrap calls to the original and the added sub into a new anon sub:
    $ perl -w sub addcoderef { my ($orig, $add) = @_; sub { $orig->(); $add->(); } } use strict; my $coderef = sub { print "SUB1\n"; }; $coderef = addcoderef( $coderef, sub { print "SUB2\n"; } ); $coderef->(); __END__ SUB1 SUB2
    but using an array scales better.

    Another option is to deparse them (but deparsing historically hasn't worked 100%):

    $ perl -w use strict; sub addcoderef { use B::Deparse; my $d = B::Deparse->new(); eval join "\n", "sub {", map($d->coderef2text($_), @_), "}"; } my $coderef = sub { print "SUB1\n"; }; $coderef = addcoderef( $coderef, sub { print "SUB2\n"; } ); $coderef->(); __END__ SUB1 SUB2
Re: subroutine concatenation
by tmoertel (Chaplain) on Feb 11, 2005 at 01:44 UTC
    One powerful way to join functions together is to "compose" them. What this means is that you glue the functions into a pipeline, so that one function is called after the other, and the output of the one becomes the other's input. To see how it works, let us consider two functions f and g:
    #! perl -l my $f = sub { "f($_[0])" }; my $g = sub { "g($_[0])" }; print $f->("x"); # f(x) print $g->("x"); # g(x)
    Now let us create a simple function compose2 that glues the two functions together into a pipeline. (We call it compose2 because it composes 2 functions.)
    sub compose2 { my ($f, $g) = @_; sub { $f->( $g->(@_) ) } }
    Now we can compose the functions we defined earlier.
    my $h = compose2( $f, $g ); print $h->("x"); # f(g(x))
    We can extend composition to any number of functions by "folding" our 2-function version compose2 over a list of function arguments. We will borrow a function foldl from the realm of functional programming to help us. (This function is very much like reduce from the List::Util module.)
    sub foldl { my $f = shift; my $z = shift; $z = $f->($z, $_) for @_; $z; } sub compose { foldl( \&compose2, @_ ) }
    Let's give it a try.
    print compose( $f,$f,$f,$f,$f,$f,$g )->("x"); # f(f(f(f(f(f(g(x))))))) my $add1 = sub { $_[0] + 1 }; my $add2 = compose( $add1, $add1 ); my $add4 = compose( $add2, $add2 ); print $add2->(0); # 2 print $add4->(0); # 4 print compose( $add1, $add1, $add1 )->(0); # 3 print compose( ($add1) x 4 )->(0); # 4
    Function composition is a simple idea – take two functions and glue them together – but you can do some surprisingly cool things with it. (See Re: Ways to implement a closure for more fun with function composition.) Maybe instead of just concatenating your functions, you might get more mileage from composing them.

    Cheers,
    Tom

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (8)
As of 2024-04-18 11:03 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found