http://qs321.pair.com?node_id=208407

Update {Please note the code at RFC: Class::Proxy::MethodChain that spun off of this snippet later and my eventual conclusion at Re: RFC: Class::Proxy::MethodChain. This code is far too elaborate an effort for something that can be had much easier; consider it an educational example, but don't go using it (in exactly this form). }

I've been doing some Gtk work lately, and if you've ever done so you'll know that code tends to turn into long runs of method calls against the same just instantiated widget, like:

my $btn = Gtk::Button->new("Quit"); $btn->signal_connect(clicked => sub { Gtk->exit(0); return }); $btn->show; my $window = Gtk::Window->new("toplevel"); $window->signal_connect(delete => sub { Gtk->exit(0); return }); $window->set_title("Test"); $window->border_width(15); $window->add($btn); $window->show;
You can imagine how bad it gets when there are 30 widgets distributed across four different containers, each object needing a dozen method calls to configure. With the following snippet, you can write this as
my $window = configure_object( Gtk::Window->new("toplevel"), signal_connect => [ delete => sub { Gtk->exit(0); return } ], set_title => [ "Test" ], border_width => [ 15 ]; add => [ configure_object( Gtk::Button->new("Quit"), signal_connect => [ clicked => sub { Gtk->exit(0); return } ], show => undef, )], show => undef, );

which I find much nicer. It lets you omit a lot of otherwise necessary temporary variables, and it also makes it clear - by way of nesting - which widgets are inserted where. In contrast, the flat structure requires you to search on the names of temporary widget variables to figure out how they all relate to each other.

It also has provision to deal with multilevel calls which are necessary for some objects:

$fileselect->cancel_button->signal_connect( clicked => sub { $fileselect->hide } ); # ...
becomes
configure_object( $fileselect, cancel_button => signal_connect => [ clicked => sub { $fileselect->hide }, ], # ... );
sub configure_object { my $object = shift; while(@_) { my ($meth, $param) = splice @_, 0, 2; my $obj = $object; until((not defined $param) or ref $param) { $obj = $obj->$meth; ($meth, $param) = ($param, shift); } $obj->$meth(@{ $param || [] }); } return $object; }

Replies are listed 'Best First'.
•Re: multiple method calls against the same object (f.ex GUI programming)
by merlyn (Sage) on Oct 28, 2002 at 15:07 UTC
    If the method calls are set up properly, you can "chain" them, like:
    (my $window = Gtk::Window->new("toplevel")) ->signal_connect(delete => sub { Gtk->exit(0); return }) ->set_title("Test") ->border_width(15) ->add($btn) ->show;
    but this requires that each of these configurator calls returns $self as the last step, which is frequently the case, although beginners don't understand why to do this because they've not seen this pattern before. Even if it doesn't, I sometimes find myself simply chaining these calls using an alias:
    my $window = Gtk::Window->new("toplevel"); for ($window) { $_->signal_connect(delete => sub { Gtk->exit(0); return }); $_->set_title("Test"); $_->border_width(15); $_->add($btn); $_->show; }
    By not repeating $window repeatedly, you make it clear in the code that you are working on the same object every time. Also, it works well if you want to configure many objects similarly.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      ...but this requires that each of these configurator calls returns $self as the last step, which is frequently the case...

      just out of curiosity, but with this design in mind how would you return a value from a method call? even a simple boolean for success/failure. for example, how could you do something like:

      $window->set_title("Test") or die "Can't set title!\n";
      if you're always only returning $self?

      cheers,
      Aldo

      King of Laziness, Wizard of Impatience, Lord of Hubris

        Just use exceptions instead. Nothing wrong with wrapping a series of these things in an eval block to catch the exception.

        I prefer exceptions when an action will likely work 95% of the time or more. That way, the testing doesn't interfere with my examination of the control flow, and you get to do nice tricks like a "return $self" chain.

        -- Randal L. Schwartz, Perl hacker
        Be sure to read my standard disclaimer if this is a reply.

      I know about the first, but it wasn't possible here and also turns ugly when you have a multilevel call somewhere in that pile.

      I was actually thinking the second is as good as I can do in this case. What I didn't like about topicalizing with a for in that case is that for one, I still have to mention the object for every method call, although at least it's only $_ now. And worse yet, I still need a temporary variable for every widget, even when it's a widget I don't care about having access to later (scrollbars often fall in this category f.ex).

      By rearranging the program to use this snippet I managed to throw away about 4 out of 5 of my variables. That's more than a little win in clarity in my book.

      But thanks for the suggestions. :-)

      Makeshifts last the longest.

        And worse yet, I still need a temporary variable for every widget, even when it's a widget I don't care about having access to later (scrollbars often fall in this category f.ex).
        In that case, it gets even easier:
        for (Scrollbar->new) { $_->config1($param1); $_->config2($param2); $otherwidget->add($_); }
        Don't name anything you don't need to name!

        -- Randal L. Schwartz, Perl hacker
        Be sure to read my standard disclaimer if this is a reply.