Oh Mighty and Venerable Monks of Perl Wisdom, I humbly ask for your advice when using anonymous subs, specifically, their impact and perils. I have spent time lately cleaning and updating several modules that contain a large amount of duplicate code, both in structure and functionality, save for a couple segments. For a fictional example of a problem similar to the one encountered, I present the following code.
sub DoTask1 { my ($object, @arguments) = @_; if (! $object->TaskIsDone() ) { $object->SetupTaskOne(); } if (! @arguments ) { @arguments = qw(ARG1 ARG2 ARG3); } return $object->CommonTask(@arguments); } sub DoTask2 { my ($object, @arguments) = @_; if (! $object->TaskIsDone() ) { $object->SetupTaskTwo(); } if (! @arguments ) { @arguments = qw(ARG6 ARG2 ARG1 ARG5); } return $object->CommonTask(@arguments); } # (...) sub DoTask10 { my ($object, @arguments) = @_; if (! $object->TaskIsDone() ) { $object->DoTask(); $object->DoSomeMoreSetup(); } if (! @arguments) { @arguments = qw(ARG3 ARG5 ARG7); } return $object->CommonTask(@arguments); }
The module ended up having many subs that look almost identical. What do we, as excellent developers do when we see a maintenance issue like this? The interface is set; people rely on these methods to be there; this makes a complete rewrite unacceptable. Instead we take what have and extract, reduce, and reuse. The first obvious thing we can do is to extract the construction of default arguments out.
sub Helper { my ($object, $defaults, @arguments) = @_; if (!@arguments) { @arguments = @$defaults; } return $object->CommonTask(@arguments); } sub DoTask1 { my ($object, @arguments) = @_; if (! $object->TaskIsDone() ) { $object->SetupTaskOne(); } return $object->Helper([ qw(ARG1 ARG2 ARG3) ], @arguments); } sub DoTask2 { my ($object, @arguments) = @_; if (! $object->TaskIsDone() ) { $object->SetupTaskTwo(); } return $object->Helper([ qw(ARG6 ARG2 ARG1 ARG5) ], @arguments); } (...) sub DoTask10 { my ($object, @arguments) = @_; if (! $object->TaskIsDone() ) { $object->DoTask(); $object->DoSomeMoreSetup(); } return $object->Helper([ qw(ARG3 ARG5 ARG7) ], @arguments); }
The code from Helper can even be folded into the CommonTask method; it's simply separate here for simplicity.

Now we see that we still redo the 'if' statement in each, but the code inside is different each time. Arrugh. Could this be a place where our helper can be enhanced? What if the helper routine took a code snippet to work on? Perl has used anonymous subs for custom logic with the 'sort' and 'map' builtins. Would it work here? Would it be worth it?

sub Helper { my ($object, $code, $defaults, @arguments) = @_; if (! $object->TaskIsDone()) { $code->(); } if (!@arguments) { @arguments = @$defaults; } return $object->common_task(@arguments); } sub DoTask1 { my ($object, @arguments) = @_; return $object->Helper( sub { $object->SetupTaskOne() }, [ qw(ARG1 ARG2 ARG3) ], @arguments); } sub DoTask2 { my ($object, @arguments) = @_; return $object->Helper( sub { $object->SetupTaskTwo() }, [ qw(ARG6 ARG2 ARG1 ARG5) ], @arguments); } (...) sub DoTask10 { my ($object, @arguments) = @_; return $object->Helper( sub { $object->DoTask(); $object->DoSomeMoreSetup(); }, [ qw(ARG3 ARG5 ARG7) ], @arguments); }
The real world equivalent resulted in a couple hundred lines of code reduction. The refactoring process also exposed a couple corner cases I had missed, and almost completely reduced the code duplication in that area of the module. The module entered final unit and functional testing, and so far I've seen little to no impact on application behavior. Unfortunately, I do not have benchmarking tools in place to document performance changes, but with this particular application the majority of delays lie outside of Perl.

Later, this intrepid but inexperienced monk realized that there was another section of common code seen in his module. Very often I'd see developers using the library write code like the following:

my $original_state = Module::GetServiceState(); Module::SetServiceState(0); # temporarily disable the service, if it's + not already $object->DoSomething(); $object->DoSomethingElse(); Module::SetStatus($original_state); # restore it to what it was before
After exploring a bit, I set up a simple subroutine containing the duplicate code, taking a single argument - the work to be done while the service is off.
sub TempDisableService { my $code = shift; my $original_state = Module::GetServiceState(); Module::SetServiceState(0); # temporarily disable the service, if i +t's not already $code->(); Module::SetStatus($original_state); return; } # and later... TempDisableService sub { $object->DoSomething(); $object->DoSomethingElse(); }; # and even later... TempDisableService sub { $object->DoSomethingMore(); $object->DoSomethingAlso(); };
Now, there's been talk in the past about prototypes. Even though most talk seems to fall in the 'against' category, using one here seemed like a good application; it wouldn't be called indirectly, and it only had the code as an argument. Using a 'code block' prototype, the syntax now looks incredibly clean:
TempDisableService { $object->DoSomething(); $object->DoSomethingElse(); };
The need for 'sub' has been eliminated. I see this making it easier for less experienced developers to get it right, but it could also introduce some confusion.

Each of the main method/subroutines only contains the data specific to it's needs, and all common functionality is in one place. So far the refactored code seems to work quite well; I haven't run into any real issues in this process.

My main concern comes from only rarely seeing anonymous subs used like this in (an admittedly small number of) CPAN modules I've looked at. If others here know of particular handicaps, issues, or caveats with anonymous subroutines in Perl, I would love to hear them, especially any kinds of new behavior might pop up when using anonymous subs. Should I even be concerned? Maybe there exists many interesting examples of anonymous subroutine out there, and I just haven't seen them.

Oh great perl monks / I see, humbly pray with thee / please enlighten me!


In reply to Caveats when using anonymous subs as subroutine arguments by Glav

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.