logo
Free, unlimited AI code reviews that run on commit
git-lrc git-lrc GitHub Install Now We'd appreciate a star git-lrc - Free, unlimited AI code reviews that run on commit | Product Hunt git-lrc - Free, unlimited AI code reviews that run on commit | Product Hunt

Aspect::Library::Wormhole - A wormhole between call frames

Authors

       Adam Kennedy <adamk@cpan.org>

       Marcel Grünauer <marcel@cpan.org>

       Ran Eilam <eilara@cpan.org>

Description

       A reusable aspect for passing objects down a call flow, without adding extra arguments to the frames
       between the source and the target. It is a tool for acquiring implicit context.

       Suppose "A::a()" calls "B::b()" calls "C::c()"... until "Z::z()".

       All is well, until one day you get a requirement with a crosscutting implication- "Z::Z()" requires one
       extra argument. It requires an instance of the class "A". The very same instance on which the method
       "a()" was called, high up the call chain of "Z::z()".

       Without this aspect you can either add a global $Current_A (very problematic), or make "A::a()" send
       "B::b()" its $self, make "B::b()" pass it on to "C::c()", and so on until "Z::z()". You are forced to add
       many arguments to many methods.

       Show me a developer who has never encountered this situation: you need to add an argument to a long call
       flow, just because someone at the bottom needs it, yet only someone on the top has it. The monkey code
       required oneachcallframe in the call flow, foreachargument that eachtarget requires, is suffering
       from EEK- Extraneous Embedded Knowledge (<http://citeseer.ist.psu.edu/254612.html>).

       The code for the frames between the two ends of the wormhole, knows more about the world than it should.
       This extraneous knowledge is embedded in each method on the call flow, and there is no easy way to remove
       it.

       This aspect removes the EEK by allowing you to setup a wormhole between the source and target frames in
       the call flow. The only effect the wormhole has on the call flow, is that the target gets called with one
       extra argument: the calling source object. Thus the target acquires implicit context.

       So this wormhole:

         aspect Wormhole => 'A::a', 'Z::z';

       Means: before the method "Z::z()" is called, if "A::a()" exists in the call flow, then append one
       argument to the argument list of "Z::z()". The argument appended is the calling "A" object.

       No method in the call flow is required to pass the source object, but "Z::z()" will still receive it.

         +--------+                                       +--------+
         | source |    +--------+    +--------+           | target |
         +--------+--> | B::b() |--> | C::c() |--> ...--> +--------+
         | A::a() |    +--------+    +--------+           | Z::z() |
         +--------+                                       +--------+
             .                                                ,
             |                                               /|\
             |                                              / | \
             |                                                |
             +------------- The Bajoran Wormhole -------------+

Name

       Aspect::Library::Wormhole - A wormhole between call frames

Synopsis

         package A;
         sub new { bless {}, shift }
         sub a { B->new->b }

         package B;
         sub new { bless {}, shift }
         sub b { C->new->c }

         package C;
         sub new { bless {}, shift }
         sub c { ref pop }

         package main;

         print ref A->new->a; # without aspect, prints C

         use Aspect::Library::Wormhole;
         aspect Wormhole => 'A::a', 'C::c';
         print ref A->new->a; # with aspect, prints A

Using

       The aspect constructor takes two pointcut specs, a source and a target.  The spec can be a string (full
       sub name), a regex (sub will match if rexep matches), or a coderef (called with sub name, will match if
       returns true).

       For example, this will append a calling "Printer" to any call to a sub defined on "Page", if it is in the
       call flow of "Printer::print":

         aspect Wormhole => 'Printer::Print', qr/^Page::/;

See Also