Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

multiple method calls against the same object, revisited

by Aristotle (Chancellor)
on Dec 25, 2004 at 11:44 UTC ( [id://417405]=CUFP: print w/replies, xml ) Need Help??

This is an ongoing theme of mine: see multiple method calls against the same object (f.ex GUI programming) and RFC: Class::Proxy::MethodChain.

I think I have now found an elegant solution I can settle for.

An example similar to the one in multiple method calls against the same object (f.ex GUI programming) would be

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

which is equivalent to

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

Update: changed example to actual working Gtk2 code.

Update: fixed re-throw code bug spotted by simonm.

use List::Util; sub call_on_obj { my( $method, @param ); no warnings 'once'; List::Util::reduce { ( $method, @param ) = @$b; eval { $a->$method( @param ) }; if( $@ ) { { local $@; require Carp; } Carp::croak( $@ ) } $a; } @_; }

Replies are listed 'Best First'.
Re: multiple method calls against the same object, revisited
by TimToady (Parson) on Dec 25, 2004 at 18:51 UTC
    In Perl 6 that's probably written like this:
    given Gtk2::Window.new( "toplevel" ) { .signal_connect( :delete{ Gtk2.main_quit } ); .set_title( "Test" ); .border_width( 15 ); .add( given Gtk2::Button.new( "Quit" ) { .signal_connect( :clicked{ Gtk2.main_quit } ); $_; } ); .show_all; }
    Though I could see extending the but operator to take a topicalized closure so we don't have to put the ugly $_ at the end:
    Gtk2::Window.new( "toplevel" ) but { .signal_connect( :delete{ Gtk2.main_quit } ); .set_title( "Test" ); .border_width( 15 ); .add( Gtk2::Button.new( "Quit" ) but { .signal_connect( :clicked{ Gtk2.main_quit } ); } ); .show_all; }
    Merry Christmas!!!

      Yeah, I knew that Perl6 would make that much nicer in the form of your first example. :-) I have to say that the but example is a lot nicer, though. Lovely!

      Except it's going to be a while yet. :-( So some way to make this nicer in Perl5 will have to do for the time being. Of my attempts so far I think this is by far the most likeable.

      Makeshifts last the longest.

      Pascal! :)

      Maybe an "on a" modifier?

      .signal_connect( :clicked{ Gtk2.main_quit } ) on Gtk2::Button.new( "Quit" );

      Indirect object syntax would make that sounds even more like English. But this is probably stretching it too far anyway.

        But how would you do multiple calls against the same thing?

        { .signal_connect( :delete{ Gtk2.main_quit } ); .set_title( "Test" ); .border_width( 15 ); .add( .signal_connect( :clicked{ Gtk2.main_quit } ) on Gtk2::Butto +n.new( "Quit" ) ); .show_all; } on Gtk2::Window.new( "toplevel" );

        There is a simple analogon in Perl5:

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

        Meh. It puts things in the wrong order IMO.

        Makeshifts last the longest.

Re: multiple method calls against the same object, revisited
by dragonchild (Archbishop) on Dec 27, 2004 at 19:50 UTC
    What's wrong with
    my $window; for ($window = Gtk2::Window->new( "toplevel" )) { $_->signal_connect( delete_event => sub { Gtk2->main_quit } ); $_->set_title( "Test" ); $_->set_border_width( 15 ); $_->add( do { my $button = Gtk2::Button->new( "Quit" ); $button->signal_connect( clicked => sub { Gtk2->main_quit } ); $button; } ); $_->show_all(); }

    $_ is the "current topic" and for() acts as a topicalizer. Unless, of course, I'm missing something ...

    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.

      There's nothing really wrong with it, but I find it's wordier than it needs to be. Look at what the Perl6 idiom looks like; much quieter visually. Trying to retrofit the construct for this use can lead to some awkward choices: f.ex, I understand why you put the assingment in the for list rather than on the my line before it, but isn't that really the wrong way around? There's no reason to pick one over the other in terms of effective length, but of course putting the assignment on the my line makes the for line stick out like a sore thumb…

      If I really wanted to use $_ as “it”, I'd probably use a do block:

      my $window = do { local $_ = Gtk2::Window->new( "toplevel" ); $_->signal_connect( delete_event => sub { Gtk2->main_quit } ); $_->set_title( "Test" ); $_->set_border_width( 15 ); $_->add( do { local $_ = Gtk2::Button->new( "Quit" ); $_->signal_connect( clicked => sub { Gtk2->main_quit } ); $_; } ); $_->show_all(); $_; };

      That mirrors the structure I achieve with my snippet most closely. But it needs those ugly $_; strewn in there and I'm still mentioning $_ a million times — a blanket of noise covering the code.

      Make no mistake, I'm not 100% satisfied with what this snippet allows, either. But until we have Perl6 in our hands, it won't be possible to do much better than it.

      Makeshifts last the longest.

Re: multiple method calls against the same object, revisited
by simonm (Vicar) on Dec 27, 2004 at 19:46 UTC
    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.

      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

      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: multiple method calls against the same object, revisited
by Anonymous Monk on Dec 28, 2004 at 10:55 UTC
    I prefer "fixing" this at the source. I tend to have methods that are purely called for their side-effects, and not for their return values, return the object they acted upon.

    And that includes accessors. Tk does it that way as well, and I like it. Example:

    sub accessor { my $self = shift; if (@_) { # Sets the attribute. $$self{key} = shift; return $self; } return $$self{key}; # Gets the attribute. }
    Then you can write code like this:
    my $obj->key1(value1) ->key2(value2) ->key3(value3);

      Except that style is awful.

      There is temptation to mix calls to setters in front of method calls with useful return values, which results in pretty confusing to read code:

      my $label = $button->border( 20 )->label;

      It gets really bad when some of the object's methods return aggregated objects. Suddenly there are multiple different kinds of call chains, and the reader has to juggle far too much knowledge to keep affairs straight.

      In the worst case you get code like this:

      my $label_formatting = $window->title( 'foo' ) ->child->border( 20 ) ->child->font_style;

      If that's not ugly as all sin, you tell me what is. Transgressions like those shown above are rare, to be sure (and thankful).

      But $self-returning mutators blur the lines between different types of methods. If you don't think it's an issue in practice, just look at the encouraged OO syntax in the Data::Dumper POD. That API actually encourages mixing mutators and with non-mutator calls. That Tk in particular does things this way does everything but speak for it; the Tk API is a mess few others get even close to.

      And methods should not return different things depending on the number of their arguments anyway.

      Makeshifts last the longest.

Log In?
Username:
Password:

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

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

    No recent polls found