One of the old/popular tricks in UN*X world is using single binary for multiple commands, for example
gzip file # compress file gunzip file # decompress file diff `which gzip` `which gunzip` #NONE!

Now, here's the problem I've got: I'm writing a simple library, which should provide two methods, get and slurp, usage scenario looks like this:

$rv=$sth->get("var"); $value=$sth->slurp("var");

Now, the problem at hand is those methods are almost completely identical, they differ in only ~2 lines ( while being ~50 lines long, and expected to grow to ~1k lines ).

Normally I would go with something like this:

sub slurp { my ($self,$stuff)=@_; $self->get($stuff,"SLURP"); } sub get { #..... if ($slurp) { # difference.. } else { #... } }
but what when you've got arrays as arguments?

So, I figured I'd use AUTOLOAD... but - first, I don't know how, and second - I'm not really sure if that's the best route... it looks like with AUTOLOAD it's all getting messy...

What do monks think?

Replies are listed 'Best First'.
Re: Should one treat method name as input?
by demerphq (Chancellor) on Jan 07, 2005 at 12:13 UTC

    Refactor the main routine to handle both cases and give it a "utility" sounding name, then make the other two routines wrappers with a nicer interface into the big daddy.

    sub big_daddy{ my ($self,$type,@args)=@_; ... } sub foo{ my $self=shift; $self->big_daddy('FOO',@_); } sub bar{ my $self=shift; $self->big_daddy('BAR',@_); }

    If you really feel like being evil, you could make $type come from caller() so that the behaviour of bigdaddy is determined by what has called it, but IMO this is nasty stuff that isnt worth bothering with.

    ---
    demerphq

      Thanks! This is exactly what I wanted.

      I have no idea why I couldn't figure it out myslef - it's simple, clean, elegantly avoids duplication of code, and requires no playing with AUTOLOAD or caller()

Re: Should one treat method name as input?
by Anonymous Monk on Jan 07, 2005 at 12:55 UTC
    eval method:
    BEGIN { for my $name (qw {slurp get}) { eval<<"TILL_HERE"; sub $name { print("This is $name\n"); } TILL_HERE } } slurp; get; __END__ This is slurp This is get
    AUTOLOAD method:
    sub slurp; sub get; sub AUTOLOAD { my ($func) = $main::AUTOLOAD =~ /.*::(.*)/; if ($func eq "slurp" || $func eq "get") { print("This is $func\n"); } } slurp; get; __END__ This is slurp This is get
    Three sub method:
    sub common { my ($name, @args) = @_; print("This is $name\n"); } sub slurp {common("slurp", @_)} sub get {common("get", @_)} slurp; get; __END__ This is slurp This is get

    All methods have their pros and cons. Most people will find the last solution the cleanest. But, without doing the benchmarks, I expect the latter to be the least efficient, specially if you have a lot of arguments. The three sub method has the disadvantage that you will always do two subcalls, and calling a sub in Perl is costly. It will also mean copying of @_ (although you could pass a reference to slurp/gets @_ to common), which is expensive if @_ is large.

    With the AUTOLOAD method no second sub is called, but additional work per sub-call is still performed (parsing of, and branching on the value of $AUTOLOAD). And using AUTOLOAD gets tricky in an OO environment, as it doesn't have native support to delegate calls it doesn't want to handle.

    Leaves us the eval method. The disadvantage is that the code looks messier, and that you will end up with a larger op-code tree, as you will compile identical code twice. The advantage is that all the extra costs are done at compiletime - there is no runtime penalty at all.

    So, what the best route is for you is only something you can decide. You have to balance between compile- and runtime efficiency, and "cleanness" of your code (which is subjective).

      The third solution is a good case for goto &sub:

      sub common { my ($name, @args) = @_; print("This is $name\n"); } sub slurp {unshift(@_,"slurp"); goto &common} sub get {unshift(@_,"get"); goto &common} slurp; get; __END__ This is slurp This is get
      This way if you carp() or croak() within common() you get better error message plus it's quicker.

      Jenda
      We'd like to help you learn to help yourself
      Look around you, all you see are sympathetic eyes
      Stroll around the grounds until you feel at home
         -- P. Simon in Mrs. Robinson

      It might be worth noting, method #3 is certainly the 'classic' method, mainly because it's the only one most other languages support...