Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Re: multiple method calls against the same object, revisited

by simonm (Vicar)
on Dec 27, 2004 at 19:46 UTC ( #417616=note: print w/ replies, xml ) Need Help??


in reply to multiple method calls against the same object, revisited

For what it's worth, I think I prefer this syntax:

my $window = Gtk2::Window->new( "toplevel" )->call_method_list( signal_connect => [ delete_event => sub { Gtk2->main_quit } ], set_title => "Test", set_border_width => 15, add => Gtk2::Button->new( "Quit" )->call_method_list( signal_connect => [ clicked => sub { Gtk2->main_quit } ] ), 'show_all', );

Implementation follows:

use Carp qw( croak ); sub UNIVERSAL::call_method_list { my $target = shift; while ( scalar @_ ) { my $method = shift @_; my @args = (! scalar @_) ? () : (ref($_[0]) eq 'ARRAY') ? @{shift} + : shift; eval { $target->$method( @args ) }; if( $@ ) { croak( $@ ) } } return $target; }

P.S.: Unfortunately, if( $@ ) { require Carp; Carp::croak( $@ ) } won't work the way you want it to, because if Carp hasn't already been required, the process of loading it clears $@, so it's not available to the croak call. You could write it as if ( my $err = $@ ) { ... croak( $err) } but I think that in practice this technique will only be used in larger, non-trivial applications, so you might as well just require Carp up front.


Comment on Re: multiple method calls against the same object, revisited
Select or Download Code
Re^2: multiple method calls against the same object, revisited
by ihb (Deacon) on Dec 28, 2004 at 02:44 UTC

    What happens when you want to have a method without any arguments in the middle of those other methods? This problem is the reason Aristotle used array references, I think.

    A possible solution would be to "escape" the methods without arguments, yielding

    my $window = Gtk2::Window->new( "toplevel" )->call_method_list( signal_connect => [ delete_event => sub { Gtk2->main_quit } ], set_title => "Test", \'my_argumentless_method', # <--------------- set_border_width => 15, add => Gtk2::Button->new( "Quit" )->call_method_list( signal_connect => [ clicked => sub { Gtk2->main_quit } ] ), \'show_all', );
    and change the implementation to (untested)
    use Carp qw( croak ); sub UNIVERSAL::call_method_list { my $target = shift; while ( scalar @_ ) { my $method = shift @_; my @args = (ref $method) ? () : (ref($_[0]) eq 'ARRAY') ? @{shift} + : shift; $method = $$method if ref $method; eval { $target->$method( @args ) }; if( $@ ) { croak( $@ ) } } return $target; }

    ihb

    Update: Apparently I should've read the code closer. The last case of a single method with no following element threw me off.

    See perltoc if you don't know which perldoc to read!
    Read argumentation in its context!

      What happens when you want to have a method without any arguments in the middle of those other methods?

      Just write it with an empty array reference:

      my $window = Gtk2::Window->new( "toplevel" )->call_method_list( signal_connect => [ delete_event => sub { Gtk2->main_quit } ], set_title => "Test", my_argumentless_method => [], set_border_width => 15, add => Gtk2::Button->new( "Quit" )->call_method_list( signal_connect => [ clicked => sub { Gtk2->main_quit } ] ), 'show_all', );
Re^2: multiple method calls against the same object, revisited
by Aristotle (Chancellor) on Dec 28, 2004 at 09:51 UTC

    Ugh. :-( Sorry, but I'm not going to be sticking stuff in UNIVERSAL:: willy nilly, particularly not to fix such a trivial complaint. You could put it in a different package and call it as in ->UTIL::invoke_methods() or something.

    In terms of parameter format, your code is quite similar to my first attempt at multiple method calls against the same object (f.ex GUI programming). Putting the method name outside the list of parameters but it's awkward rather than convenient in practice. In my code I did it because I wanted to be able to represent sub-callchains as in

    $obj->foo; $obj->child->bar; $obj->baz;

    but it's still too limited anyway because you can't pass parameters to intermediate methods. Putting the methodname inside the arrayref actually allows for syntax to handle this. (You put nested arrayrefs in front of the name of the method to call, easily distinguished because the name must be a string.)

    Another option might be something like

    sub CHAIN::AUTOLOAD { my $self = shift; my ( $method ) = ( our $AUTOLOAD =~ /.*::(.*)/ ); $self->$method( @_ ); $self; } # now we can say my $window = Gtk2::Window ->new( "toplevel" ) ->CHAIN::signal_connect( delete_event => sub { Gtk2->main_quit } ) ->CHAIN::set_title( "Test" ) ->CHAIN::set_border_width( 15 ), ->CHAIN::add( Gtk2::Button ->new( "Quit" ) ->CHAIN::signal_connect( clicked => sub { Gtk2->main_quit } ) ) ->CHAIN::show_all();

    but again, ugh.

    Trust me, I've been around the block a number of times with this one. All of the “solutions” suck, the one in the root node just IMHO sucks a little less than all the others. We need Perl6.

    Update: oh, and thanks for the heads up on $@!

    Makeshifts last the longest.

      Here is a similar solution inspired by your code.

      package Chain; sub new { my $proto = shift; my $self = {obj => shift}; bless $self, $proto; return $self; } sub AUTOLOAD { my $self = shift; my ( $method ) = ( our $AUTOLOAD =~ /.*::(.*)/ ); $self->{obj}->$method( @_ ); $self; } 1;

      Now we can just do Chain->new($obj) and call methods on that object expecting the object itself as a return. I'm sure this could be cleaned up to handle errors etc.

      use strict; use warnings; use lib "."; use Chain; use Test; my $t = new Test; $t->hello; $t->cool(1,2); Chain->new($t) ->test ->hell(1,2,3) ->say("hello");

      ___________
      Eric Hodges
        How is this a gain over Class::Null? For more info, take a look at The Null Mull (or, when OO needs more O)

        Being right, does not endow the right to be rude; politeness costs nothing.
        Being unknowing, is not the same as being stupid.
        Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
        Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

        Yeah, you pretty much just wrote what I did in RFC: Class::Proxy::MethodChain. Now make a root node about it, get some well-reasoned skeptic comments, mull over it for a year or so, and then write the code I posted in the root node of this thread. :-) As I said, I've gone through at least a dozen approaches and logical next steps with this thing and it's not likely you'll be able to surprise me as long as you stick to the obvious ideas — sorry.

        The problem with this group of solutions, as others said in that thread and as I have come to understand with the experience gained since that node (see Mutator chaining considered harmful), is that method chaining is not a good idea.

        Makeshifts last the longest.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://417616]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others cooling their heels in the Monastery: (10)
As of 2014-09-02 08:02 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My favorite cookbook is:










    Results (20 votes), past polls