Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

How to best pass on the context / wantarray?

by Corion (Patriarch)
on Mar 26, 2006 at 17:58 UTC ( [id://539299]=perlquestion: print w/replies, xml ) Need Help??

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

I'm looking for a way to eliminate common code in a lot of methods that deals with passing on the call context. Any suggestions on how I could restructure my code to be less repetitive are welcome.

I have a set of methods that have a dual life. When called in void context, they act as mutators, and when called in non-void context, they return a modified copy. As an example (and my use case), consider the methods to move the cursor of a text editor through a document.

The idea is to use them in the following setting:

$cursor->move_right(3); # move cursor three chars to the right $cursor->move_down(); # move cursor one line downwards # and my $left_pos = $cursor->move_left(1); # position one char to the left of the cursor my $start = $cursor->start_of_document();

I have many more such methods (end_of_document, ...) and they all look basically the same:

sub frobnicate_position { my ($self) = @_; my $obj; if (defined wantarray) { $obj = $self->copy(); } else { $obj = $self; } ... calculate new position here, ... set $obj->line() and $obj->column() return $obj };

For every method, the calculation can range from pretty simple to moderately complex, but the calculation code seldom spans more than three lines.

What I'm aiming for is to eliminate or reduce the 8 common lines of setup that every method will have, as the repetition strikes me as not elegant. One idea I have is to use a wrapper around the real meat:

sub positional_mutator(&;$) { my ($worker,$name) = @_; no strict 'refs'; *{$name} = sub { my $self = shift; my $obj; if (defined wantarray) { $obj = $self->copy(); } else { $obj = $self; }; $worker->($obj,@_); } }; positional_mutator { my ($obj,@args) = @_; ... calculate new position here }, 'frobnicate_position';

That way, I'll eliminate these setup lines evaluating the context, at the price of some syntactical weirdness - I'll have to install the method through my wrapper and the name now comes after the code. That isn't completely elegant. I could go through Attribute::Handler, but that strikes me as the nuclear solution to weed removal - it also doesn't play well when the module isn't loaded through use, at least when loading the module by use_ok.

Replies are listed 'Best First'.
Re: How to best pass on the context / wantarray?
by BrowserUk (Patriarch) on Mar 26, 2006 at 18:18 UTC

    I'm probably missing something, but isn't that just

    sub frobnicate_position { my $obj = defined wantarray ? $_[0]->copy() : $_[0]; ... calculate new position here, ... set $obj->line() and $obj->column() return $obj };

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

      D'oh - talk about not seeing the forest for the trees :-)

      Of course, any other suggestions are still welcome, but I guess it'll be hard to improve on BrowserUK's suggestion

Re: How to best pass on the context / wantarray?
by jdporter (Chancellor) on Mar 26, 2006 at 23:32 UTC

    BrowserUK's response is good, I guess, but I want to offer the following because it uses an interesting but rarely used feature of perl.

    sub which_obj { # examine the calling sub's 'wantarray': defined( (caller(1))[5] ) ? $_[0]->copy : $_[0] }
    Then you can:
    sub foo { . . . my $obj = $self->which_obj; . . . }
    We're building the house of the future together.
Re: How to best pass on the context / wantarray?
by GrandFather (Saint) on Mar 26, 2006 at 18:27 UTC

    This, on the face of it, seems to be a Bad Idea™. Cute, but bad. There may be situations where it makes sense to suborn a verb to report a status depending on calling context, but this is not a good example. If I see a sub called move_xxx I expect movement to happen. The implication here is that the calling object is updated or not depending on calling context, and as we know calling context can be rather subtle at times! What is the context in the following case for example?

    get_wibble_cursor ()->move_xxx ()

    Although it has nothing like the cuteness factor, simply using a differently named method makes the code both easier to write and to understand, and probably shorter too. In this case move_xxx and moved_xxx would be good pairs.


    DWIM is Perl's answer to Gödel

      I'm not yet set on ->move_left and ->move_right - these two will maybe just become ->left and ->right, so the expectation of movement vs. result gets more ambigous again.

      The idea of using move and moved isn't bad, but is a bit too wordy for my taste. I could create the move version of a moved method automatically, so I'll keep that in mind as the alternative route to take when my dual-life idea doesn't work out. One thing that speaks for your approach is, that your approach is easily translated to JavaScript while I'm not aware of anything like wantarray in JavaScript.

        This sort of issue arrises often with respect to overloading of functions in languages like C++. In general the name of the function should say it all so that when you are reading the code you don't have to go looking elsewhere to discover subtle, but important, details about the parameters and return results.

        However this can lead to "identifiers that tell a story" which is even worse - hard to type and hard to read. There is a real art to creating good identifiers!

        The other aspect of this is a tension between a rich interface and a lean interface. A rich interface gets harder to use and to maintain. A lean interface tends toward ambiguity and missing functionality. This tension also plays part in the art of programming.


        DWIM is Perl's answer to Gödel
Re: How to best pass on the context / wantarray?
by dragonchild (Archbishop) on Mar 27, 2006 at 01:23 UTC
    DBM::Deep does the following which is inspired by CGI's self_or_default()
    sub floober { my $self = shift->_get_self; # Do stuff here with the actual object from tied() as opposed to t +he potentially # blessed item that is tied. }
    It's a method because this allows me to overload how to get to the tied method in either DBM::Deep::Array or DBM::Deep::Hash.

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

      Something like this was my first idea too, but that doesn't preserve the calling context - wantarray will always be defined in ->_get_self. jdporter's method would solve that problem of course, by inspecting the context one calling level higher.

        I don't think you want the calling context thing. I've been trying to figure out how I would use your proposed API and all it's doing is confusing me. Grandfather said it right.

        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

        _get_self() doesn't need to have the same context as its caller, it just needs to know the context of its caller. So it is as simple as:

        my $self= shift(@_)->_get_self( wantarray );

        - tye        

Re: How to best pass on the context / wantarray?
by CountZero (Bishop) on Mar 26, 2006 at 18:20 UTC
    I would be perfectly happy with the repeated code lines. When writing new methods, all you have to do is some quick copy and paste.

    Adding a wrapper method strikes me as "cleverness for the sake of cleverness".

    CountZero

    "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law

      "cleverness for the sake of cleverness".

      It might seem that way, until you find bug in those 8 lines and have to fix them everywhere you find them. cut-and-paste still yells "your doing something the hard way" to me. ;)


      ___________
      Eric Hodges
      I don't agree, since you blow up the size of the code. That makes it harder to work with quite often.

      I had a similar problem once with a hobby project (don't ask).

      There were states in a state engine, where the individual states were implemented as methods.

      At the start of all the state methods there were some admin code (to unpack parameters) and also when returning (the return values were data structure that told the management system what to do with the next state).

      The first part, the in parameter handling, became intolerable. Often the setup was half the code. I should have had some configuration that dynamically generated code, or munged the symbol table, or something.

      The ugly extra at the return values were ok. Didn't even look that bad, in the end.

      IMHO, what works best is a matter of taste, depending upon the code you add the extra lines to. (-: I would probably bet money that both you and Corion has better taste than me. :-)

        There is no accounting for taste!

        Strange enough I much like these (traditional) incantations at the beginning of subroutines and object-methods. It gives some kind of homely feeling.

        CountZero

        "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law

Re: How to best pass on the context / wantarray?
by Roy Johnson (Monsignor) on Mar 27, 2006 at 16:44 UTC
    Rather than relying on (possibly subtle) context or having two versions of each method, you might want to have the copy method and the mutator method and call them both when you want a modified copy. That seems the most clear (and flexible) to me. The overhead is not particularly onerous.
    $cursor->move_right(3); # move cursor three chars to the right $cursor->move_down(); # move cursor one line downwards # and my $left_pos = $cursor->copy->move_left(1); # position one char to the left of the cursor my $start = $cursor->copy->start_of_document();

    Caution: Contents may have been coded under pressure.
      Thank you. I was wondering after reading the original post why he was using the context (list or scalar) to determine whether to operate on a copy or the original. What does "a copy or the original" have to do with "list or scalar context" in general? Nothing! If you want to operate on a copy, grab a copy and operate on it.

      Although, I might be misunderstanding what is going on.
        The difference was between void or non-void context, not between list or scalar.
Re: How to best pass on the context / wantarray?
by demerphq (Chancellor) on Mar 27, 2006 at 16:30 UTC

    Context propagates.

    sub handle_mutator { my ($self,$cb)=@_; my $obj; if (defined wantarray) { $obj = $self->copy(); } else { $obj = $self; }; $cb->($obj,@_); } sub something { shift->handle_mutator(sub{ .... }); }
    ---
    $world=~s/war/peace/g

Re: How to best pass on the context / wantarray?
by nothingmuch (Priest) on Mar 27, 2006 at 14:48 UTC
    Update: err, crap... gotta learn to read before posting. It's still nice spam though ;-)

    I am going to release Context::Handle pretty soon but Want needs to be updated.

    It looks like this:

    use Context::Handle; sub wrapper { my $rv = context_sensitive { call_something_else() }; # do anything here $rv->return; }

    Update: I'm not sure what Anonymous Monk meant, but If said monk is implying this module is useless then said monk should learn to read. The module propagates context opaquely, such that the wrapping sub doesn't need to care about wantarray/Want at all, and evaluates the sub call in the correct context without too much PITA.

    -nuffin
    zz zZ Z Z #!perl
    A reply falls below the community's threshold of quality. You may see it by logging in.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (6)
As of 2024-03-19 08:32 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found