Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Method Chaining and Accessors

by linenoise (Acolyte)
on Apr 04, 2007 at 23:54 UTC ( [id://608386]=perlquestion: print w/replies, xml ) Need Help??

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

G'day Monks!

I have an object that has a lot of configuration options that I set using accessors:

my $obj = My::Class->new(); $obj->foo("foo"); $obj->bar("bar");
Then I figured out that if I return $self from each accessor I can chain my method calls:
my $obj = My::Class->new()->foo("foo")->bar("bar");
This is all fine and dandy until I actually want to know what $obj->foo is set to:
my $foo = $obj->foo; # Wrong! Just gets a copy of $obj.
Is there any way I can detect that I'm using the method in a chain and return the object or that I'm using it as an accessor and return the value?

I found this which seems to be a generic way of doing what I want but I can't decipher the magic contained in there.

I know Best Practices™ say that I should have separate setter and getter methods and I suspect that's the way I will have to go but having a single method that does both is just so darn elegant!

Any ideas?

Replies are listed 'Best First'.
Re: Method Chaining and Accessors
by mreece (Friar) on Apr 05, 2007 at 00:03 UTC
    can't you just test for @_?
    sub foo { my $self = shift; # oops, forgot this line before if (@_) { $self->{foo} = shift; return $self; } return $self->{foo}; }
    updated to shift off self. thanks, Moron
      *slaps forehead*

      Gee, way to make a guy feel stupid! :-)

      Thanks!

      Almost, but because the object arrives in $_[0] either way, you need to shift it out before testing @_, e.g. ...
      sub foo { my $self = shift; @_ or return $self -> {foo}; $self ->{foo} = shift; $self; }

      -M

      Free your mind

Re: Method Chaining and Accessors
by f00li5h (Chaplain) on Apr 05, 2007 at 01:25 UTC

    TheDamian suggests the use of get_foo and set_foo.This is because you have some attributes for which there will be a get_foo but no set_foo so the example was that a foo sub that is both a getter and setter has no way to indicate that you can not actually set foo, because if it dies then you have to wrap all your get/setters in eval { ... } and nobody wants that.

    package Foo; use Readonly my $SPAM_FACTOR = 22; use Readonly my $SPAM_FUDGE = 42; # seasonally adjusted # cause-cause-bundles-of-problems constructor sub new { bless {spam=>4} , shift } # you can get or set bar with $that->bar sub bar { my ($this, $newbar) = @_; if (@_) $this->{bar} = $newbar; return $this->{bar}; } # you can't set baz, because it's derrived automatically from other at +tribute sub baz { my ($this) = @_; # just ignore @_ ... perhaps warn "you cant change baz" return $this->{spam} * $SPAM_FACTOR + $SPAM_FUDGE; } # some time later pacakge main; my $foo = Foo->new(); $foo->bar( 'moose' ); # neat, bar is moose $foo->baz( 'shoe' ); # not so much, worst of all, it looks right! printf "I have a lovely foo, it has %s bar and %s baz " , $foo->bar, $ +foo->baz; # hey! where's my shoe!?

    if you use the get_bar and a set_bar convention, perl will complain bitterly when you call set_baz when the pacakge Foo does not provide one and you'll know straight away that you can't set_baz after all.

    @_=qw; ask f00li5h to appear and remain for a moment of pretend better than a lifetime;;s;;@_[map hex,split'',B204316D8C2A4516DE];;y/05/os/&print;

      TheDamian suggests the use of get_foo and set_foo.This is because you have some attributes for which there will be a get_foo but no set_foo so the example was that a foo sub that is both a getter and setter has no way to indicate that you can not actually set foo, because if it dies then you have to wrap all your get/setters in eval { ... } and nobody wants that.

      A good point, but I think you can make the case even more simply: undef is a useful value, and sometimes you want to set something to undef. If you use undef to indicate you want to do a get rather than a set, then you're stuck. So yeah, explicit getters & setters are good, and mutators aren't worth the saving of a few lines of code.

      Myself, I'm inclined to name my setters "set_*" but to avoid using the prefix "get_" on my getters. I think that reads a little more naturally and helps to distinguish between the two:

      my $attribute = $self->attribute; $self->set_attribute( $attribute );

        Myself, I'm inclined to name my setters "set_*" but to avoid using the prefix "get_" on my getters

        But then your getters look exactly like the mutators you're trying to avoid! infact, your getters have exactly the same surprise toy inside!

        my $foo = Foo::Doom->new(); $foo->bar( 'shoe' ); # OH NOES! its just a getter # but it still looks right -_- # whereas my $f00 = Foo::f00li5h->new( in_accordance_with => TheDamian ); $f00->get_bar( 'shoe' ); # what the hell is 'shoe' there for? # this is clearly a getter

        Sure if you've got a bundle of other setters called, as set_foo in exactly the same place that you're adding the new code to set bar, you're a little less likely to write $foo->bar('shoe'), but only a litte.

        Personaly, I'd expect $foo->bar to be a mutator.

        @_=qw; ask f00li5h to appear and remain for a moment of pretend better than a lifetime;;s;;@_[map hex,split'',B204316D8C2A4516DE];;y/05/os/&print;

        Here's an accessor/mutator that is set up for chaining and will accept undef as a value.

        sub foo { my $self = shift; # Set attribute if any args if ( @_ ) { $self->{foo} = shift; #validation is for chumps return $self; } else { return $self->{foo} } die "Whiskey Tango Foxtrot"; } # cotrast with this version, which I think you had in mind sub undefoo { my $self = shift; my $newfoo = shift; if ( defined $newfoo ) { $self->{foo} = $newfoo; # What, me validate input? return $self; } else { return $self->{foo}; } die "Whiskey Tango Foxtrot"; }
        print $obj->foo(undef), "\n"; # Prints object stringification print $obj->undefoo(undef), "\n"; # prints a "\n"

        I don't know how I feel about chaining mutators. I like accessor/mutators that always return the attribute value.


        TGI says moo

Re: Method Chaining and Accessors
by friedo (Prior) on Apr 05, 2007 at 01:34 UTC

    While chaining is neato and all, I can't think of a good reason to support this use. What's wrong with simply passing a hash of configuration information to your constructor? This seems far more reasonable to me:

    my $obj = My::Class->new( foo => 'foo', bar => 'bar' );

    Under the hood, your constructor can either modify the object directly or call the accessors/mutators and let them do the work.

      The client can pass silly things in the hash, and the writer of the class has to validate that all required keys are present, and extranious ones cause warnings. unless you have Class::Std do it all for you.

      (and to be pedantic, you're not passing a hash at all)

      Update I forgot to mention that I did actually agree with the "passing data to the constructor" idea.

      @_=qw; ask f00li5h to appear and remain for a moment of pretend better than a lifetime;;s;;@_[map hex,split'',B204316D8C2A4516DE];;y/05/os/&print;
        Class::Std is definitely worth a look-see. It automates a lot of the messiness you (the OP) are trying to avoid.

        cat >~/.sig </dev/interesting

      While chaining is neato and all, I can't think of a good reason to support this use.

      I can. Lots. There's many a time you can't stuff everything into a constructor, and then live with the object ever after. Sometimes you have to twiddle the object repeatedly during the life of the program. In that context, method chaining usually results in more compact, clearer code.

      Take a look at this review of HTML::CalendarMonth, which is an example of where I think chained methods would be a big win.

      • another intruder with the mooring in the heart of the Perl

      While chaining is neato and all, I can't think of a good reason to support this use. What's wrong with simply passing a hash of configuration information to your constructor?

      Besides what grinder has already mentioned, there's an inability to force the order of the operations.

      For instance, in SOAP::Lite, there are some settings (eg, 'service') that affect mutliple parameters, so I need to ensure that they're called first, and then the ones I want to individually override.

      Now, in your case, you passed a list, so the method could just splice off the front and make sure they were applied in the correct order -- if you had used a hash, that wouldn't have been possible.

[OT] Re: Method Chaining and Accessors
by crashtest (Curate) on Apr 05, 2007 at 01:10 UTC

    This is a bit off-topic, but the first thing that popped into my head at the term "method chaining" was the common Smalltalk idiom for what you are trying to do:

    | myObj | myObj := SomeClass new method1: foo; method2: bar; yourself.
    I always thought it was very elegant and concise. method1 and method2 can return whatever they want. The yourself then hands back the newly created object.

    Of course in Smalltalk, you would have to write separate getter and setter methods (though you could overload the method name).

Re: Method Chaining and Accessors
by linenoise (Acolyte) on Apr 05, 2007 at 04:46 UTC
    Thanks for all your comments. It's been interesting reading! I'm a Unix system administrator by trade and even though I've been using Perl since the early 90s most of the time I'm just using it for quick and dirty hacks to get something working now. I relish the chance to do more advanced things with it, hence my question today.

    I guess the main reason I wanted to use chained methods is because I've seen them used elsewhere and when it came to writing this current application I figured I'd try to figure them out for myself. Not because I particularly need them but just because I was curious. :-)

    One of the things I love about Perl is that once you know the basics you can figure out how to do a lot of complicated things just by "guessing." So, in this instance I thought "I wonder if all I need to do is return $self to get chaining to work?" It did work but broke something else which is what prompted my question here.

    I'll never forget when I found out about hashes of hashes. It turned out that I'd been using things like $foo{'bar'}{'baz'} for years because it just seemed logical to me to do it but I never realised that what I was doing actually had a name and that what I was really doing was storing an anonymous hash with a key of 'baz' in another hash with a key of 'bar'. Once I figured that out it opened up a whole world of complex data structures but it all stemmed from me just assuming that I could put multiple keys on a hash to create a multi-dimensional array and Perl would DWIM.

    Anyway, that's just my longwinded way of saying thanks! I've been lurking here for a while reading people's questions and answers but this was my first question and even though it had a simple answer it has made me think a lot more about what I'm doing!

Re: Method Chaining and Accessors
by misterwhipple (Monk) on Apr 05, 2007 at 03:09 UTC
    Have a look at Contextual::Return. It lets you detect the calling context more easily and in greater detail, and explicitly specify different return values.

    cat >~/.sig </dev/interesting
      I've had a play with C::R. While you can do really neat stuff with it, the problem is that others can do what they consider to be really neat stuff with it.

      This is the same problem that's had with operator overloading in C++. Take the relatively innocuous statement i = j + k;. Do you know what's going on? Really? What would happen if I told you what classes i, j, and k were. Does that help? Oh, it doesn't?

      99% of Perl coders doesn't understand basic list, scalar, and void contexts. Now, you're going to throw in a gazillion other contexts? Maybe not such a good idea.

      Oh, and it imposes a 2-4x runtime overhead. (Note - I make this point last for a reason.)


      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?
        I remember reading mention of the performance hit. Dragonchild: Was your C::R experience before or after last month's release?

        cat >~/.sig </dev/interesting

Re: Method Chaining and Accessors
by dragonchild (Archbishop) on Apr 05, 2007 at 03:33 UTC
    Use Moose.

    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?
Re: Method Chaining and Accessors
by exussum0 (Vicar) on Apr 05, 2007 at 22:23 UTC
    The bad thing about method chaining is debugging a line that's in a chain. You can step INTO a line, but if you don't know which part of the chain is busted before hand, you have a lot of chaining in and out. If one does something like...
    $x->y(); $x->z(); $x->moo();
    You're more likely to get an easier to diagnose report should something go odd. Also, it won't matter if y(), z() or moo() returned anything at all. It's a minor quibble that becomes defensive programming.
Re: Method Chaining and Accessors
by talexb (Chancellor) on Apr 05, 2007 at 16:53 UTC

    I wonder if instead of

    my $obj = My::Class->new()->foo("foo")->bar("bar");

    you couldn't do

    my $obj = My::Class->new({ foo => 'foo', bar => 'bar'});

    That would make things a little more clear than trying to decipher the layers of arrows. And you could also punctuate it differently ..

    my $obj = My::Class->new({ foo => 'foo', bar => 'bar' });

    .. depending on your taste.

    Alex / talexb / Toronto

    "Groklaw is the open-source mentality applied to legal research" ~ Linus Torvalds

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others lurking in the Monastery: (2)
As of 2024-04-19 18:16 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found