Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

RFC: Class::Proxy::MethodChain

by Aristotle (Chancellor)
on Feb 20, 2003 at 22:06 UTC ( #237243=perlmeditation: print w/ replies, xml ) Need Help??

I just wrote a module based on a concept that has been floating around in my head for a while. It looks fairly complete already, but is just an initial draft. I'd like some comments on what I should be wary of, what could be done better, things I might add or improve, and so on. Basically, any kind of feedback you come up with - I wanna hear it.

It follows the same goal as my quick multiple method calls against the same object (f.ex GUI programming) snippet - I suggest you take a quick look at it before reading on.

To reiterate, the idea is to achieve a smoother experience for those who're using classes with many setter methods that don't usually return anything meaningful or interesting. One such module is Gtk, the Perl binding of the GTK+ toolkit.

The idea is rather simple: instead of calling methods directly on the object in question, you call them on a proxy object wrapped around it. The proxy methods return $self after storing away the return value, so now you can chain calls to multiple methods.

Of course you can also retrieve the return value of the last method call of the chain.

Furthermore, to make an obvious concept a little more exciting, there are provisions for the case of methods returning aggregated objects, such as $file_dialog->ok_button->signal_connect(..);. In that case you can pass a coderef, which will be called with the results as its parameters. With an additional convenience function that assumes a single object reference as the returned value and wraps it into a proxy of its own, you can call another chain of methods on the return value of a method such as a ->ok_button in the above example.

Once I had my goals and desired approach straight, there was surprisingly little code to write:

#!/usr/bin/perl -w use strict; package Class::Proxy::MethodChain; use vars qw($AUTOLOAD); use constant OBJ => 0; use constant RET => 1; # handles method and constructor delegation sub AUTOLOAD { my $self = shift; $AUTOLOAD =~ s/^.*:://; if(ref $self) { $self->[RET] = [ $self->[OBJ]->$AUTOLOAD(@_) ]; $self; } else { bless [($self->$AUTOLOAD(@_))x2]; } } # wrap existing object sub wrap { my $self = shift; bless [(shift)x2]; } # get last return value or pass it to a callback without # breaking the chain sub ret_val { my $self = shift; if(@_) { my $callback = shift; local *_ = $self->[RET]; &$callback(@_); return $self; } else { return @{$self->[RET]}; } } # convenience function for when the return value is an objref sub ret_wrap { my $self = shift; my $wrapped = __PACKAGE__->wrap($self->[RET]->[0]); if(@_) { my $callback = shift; $callback->($wrapped); return $self; } else { return $wrapped; } } # allow calling methods with the same names as CPMC's sub call { $AUTOLOAD = shift; goto &AUTOLOAD; } sub DESTROY() {} 1;
Using this, the code from multiple method calls against the same object (f.ex GUI programming) reads a lot more naturally:
my $window = Gtk::Window ->Class::Proxy::MethodChain::new("toplevel") ->signal_connect(delete => sub { Gtk->main_quit }) ->set_title("Test") ->border_width(15) ->add(Gtk::Button ->Class::Proxy::MethodChain::new("Quit") ->signal_connect(clicked => sub { Gtk->main_quit }) ->show ) ->show;
I intend to put this on CPAN, so I'd really appreciate comments.

Makeshifts last the longest.

Comment on RFC: Class::Proxy::MethodChain
Select or Download Code
Re: RFC: Class::Proxy::MethodChain
by Aristotle (Chancellor) on Feb 21, 2003 at 15:54 UTC

    I've thought about this some more, and written a few more examples.

    This example shows the translation of using aggregate object accessors to the appropriate C:P:MC syntax:

    Before:

    my $file_dialog = Gtk::FileSelection->new("File Selection Demo"); $file_dialog->ok_button->label("Load"); $file_dialog->ok_button->relief("half"); $file_dialog->ok_button->width(80); $file_dialog->ok_button->height(50); $file_dialog->ok_button->signal_connect(clicked => sub { print $file_dialog->get_filename(), "\n"; }); $file_dialog->cancel_button->label("Exit"); $file_dialog->cancel_button->relief("half"); $file_dialog->cancel_button->width(80); $file_dialog->cancel_button->height(50); $file_dialog->cancel_button->signal_connect(clicked => sub{Gtk->main_q +uit}); $file_dialog->set_filename("penguin.png"); $file_dialog->signal_connect(destroy => sub{Gtk->main_quit}); $file_dialog->show();
    After:
    my $file_dialog; $file_dialog = Gtk::FileSelection ->Class::Proxy::MethodChain::new("File Selection Demo") ->ok_button->ret_wrap(sub {shift ->label("Load") ->relief("half") ->width(80) ->height(50) ->signal_connect(clicked => sub { print $file_dialog->get_filename()->ret_val, "\n"; }); }) ->cancel_button->ret_wrap(sub {shift ->label("Exit") ->relief("half") ->width(80) ->height(50) ->signal_connect(clicked => sub{Gtk->main_quit}); }) ->set_filename("penguin.png") ->signal_connect(destroy => sub{Gtk->main_quit}) ->show();

    I think it's easy to see that the latter syntax scales much better.

    I had been thinking that the ->ret_val() syntax is just too bulky and was toying with the idea of having AUTOLOAD() extract pre(or suf)fixes from the called method's name. Eventually I decided against it - I don't think it's better as it'll only cover one case (albeit probably the most or second most common one - getting the return value of the last method call) and complicate AUTOLOAD() needlessly. A name change on C:P:MC's methods would do just as good a job without the added hassle.

    I'm thinking of ->_ret() instead of ->ret_val() and ->_proxy() instead of ->ret_wrap(). ->ret_val() is horribly named to begin with, in retrospect - a function can return more than a single value, after all. I am leaning on ->_proxy() because ->_wrap() strikes me as indescriptive.

    Now I know underscores are usually used for private methods and that's actually exactly the reason I'd like to use them here. It's unlikely that another module will expose public methods with such names, which minimizes the potential need to reach for the ->call() escape. For the same reasons I'm pondering renaming it to ->_call().

    Of course all that means the ->wrap() constructor becomes ->proxy() as well for consistency and leads me to wondering if it's not maybe a good idea to make that one an exported function instead of a constructor. That way no C:P:MC method would start without an underscore, so collisions with wrapped object's public methods' names are far less likely.

    Makeshifts last the longest.

      I think it's easy to see that the latter syntax scales much better.

      I don't see that. Even with your short example and, I think, a good understanding of your module, I find the "before" much easier to read than the "after." I'm having a hard time even determining whether the "after" version is correct or not. For instance, you are creating a closure within a method call within an assignment to the very variable your closure uses... does that work? What is the value of $file_dialog there?

      The fact that you can't prevent collisions between your methods and those of the class you are proxying is a real problem. The best you can do is make the collisions less likely by requiring the user to deal with uglier method names. Breaking tradition and using underscores for public methods doesn't seem satisfactory. (Wouldn't you rather reinforce a tradition which makes good sense?)

      It seems that all your module is really doing is providing some syntactic sugar to avoid some typing. You can make the "before" code cleaner without resorting to OO contortions. One way to clean it up a bit:

      my $file_dialog = Gtk::FileSelection->new("File Selection Demo"); { my $b = $file_dialog->ok_button; $b->label("Load"); $b->relief("half"); $b->width(80); $b->height(50); $b->signal_connect(clicked => sub { print $file_dialog->get_filename(), "\n"; }); } { my $b = $file_dialog->cancel_button; $b->label("Exit"); $b->relief("half"); $b->width(80); $b->height(50); $b->signal_connect(clicked => sub { Gtk->main_quit }); } $file_dialog->set_filename("penguin.png"); $file_dialog->signal_connect(destroy => sub { Gtk->main_quit }); $file_dialog->show();
      And you could do quite a bit better if you wrote a little utility function to handle the common button initialization.

      In summary, I don't see a real benefit to using this module. I would avoid it as it doesn't seem to do much but provide a small syntactical shortcut, and that, IHMO, at the cost of clarity. For what it's worth, I can't think of a better way to do what you are attempting to do. (I just wouldn't attempt it at all.)

      -sauoq
      "My two cents aren't worth a dime.";
      
        For instance, you are creating a closure within a method call within an assignment to the very variable your closure uses... does that work?
        Look carefully. The my declaration stands by itself at the top, in a separate statement.
        What is the value of $file_dialog there?
        That closure is a callback; nothing is called on $file_dialog at the time of its definition, just as one should expect from any other Perl closure. The value of $file_dialog will be a reference to the C::P::MC object wrapping a fileselect box widget, at the time the closure actually gets called.
        all your module is really doing is providing some syntactic sugar to avoid some typing.
        Correct, except for the "some" bit.
        One way to clean it up a bit:

        Frankly, yuck. I'd rather use the style from my before example than break all rules of indentation - at least with the before example's style, I can use Vim's "visual block" editing features (select a rectangular area, edit one line, same changes get applied to all the line fragments within the selected block) to make my life easier. And it doesn't break my autoindent.

        Also, consider that very simple Gtk Perl app I've written. It's just a main window, a few buttons, a scrollable columnated list, a statusbar and a fileopen dialog - in other words, about the most minimal amount of GUI to do something useful. The code weighs in at well beyond two dozen widgets to set up - imagine how many variables that translates to. Now consider that the list of method calls against each is typically eight to a dozen lines and you get an idea for how much redundancy there is.

        My sense of Once And Only Once nearly caused me seizures.

        What makes it even more revolting is that the majority of these are actually going to be temporary variables, because once I've set up the corresponding widget it does its job on its own and I am no longer interested in it at all. (Scrollbars come to mind.)

        merlyn addressed that with a topicalizing for, but then you have the problem that you must weave calls against different objects, which gets really messy real quick like. And it breaks down as soon as you nest more than one widget deep, such as a Button in a VBox in a Window, because you must weave calls against different objects in the same block, and "it"/$_ can only alias one object at a time.

        There is no good way to get rid of the temporaries without writing a layer of my own syntactic sugar to insert between the main program and the widgets.

        And you could do quite a bit better if you wrote a little utility function to handle the common button initialization.

        (You realize that these are semicontrived examples. There is hardly any redundancy in method name and parameter set patterns in actual code.)

        Did you take a look at multiple method calls against the same object (f.ex GUI programming)? It is indeed just a utility function, albeit admittedly not what you were talking about. The idea behind that snippet evolved into this module. That snippet does indeed work well; I get rid of nearly all temporaries and only need to mention the object of interest once. But using it is not a satisfactory solution in the long run - it introduces its very own (and very clumsy) syntax.

        Consider on the other hand, as merlyn points out in reply to aforementioned node, that properly set up OO modules in Perl will allow chained method calls natively. Returning $self is what one should generally do wherever there's nothing else to do. Examples include Tk and Data::Dumper.

        Gtk doesn't make the list.

        Using C::P::MC, then, allows me to reclaim the benefit of established Perlisms.

        GUI programming is verbose by definition. Setting up 5 properties each on 30 widgets is just going to take a lot of code whichever way you turn it. It helps if at least you don't have to type everything in quadruplicate (or more).

        For all these reasons, I am not going to abandon this idea.

        However, I am not pretending to have arrived at the best possible solution.

        Breaking tradition and using underscores for public methods doesn't seem satisfactory.

        It was more of an idle thought, and I agree with your point. Yes, the fact I can't prevent collisions is a real problem I'll have to get around.

        One idea might be to move all functionality into AUTOLOAD; I could then add another "method name" like to_inner that serves to set an escape flag causing the next call to be passed down to the real object without examining the called method name. That would actually prevent collisions, albeit at the cost of verbose syntax when requesting so.

        The likelyhood of that happening could be reduced by consolidating other functions (ret_wrap can be merged into ret_val and behaviour be selectable via a parameter flag) or using another unlikely prefix such as something that involves a double underscore; maybe p__foo where p is for proxy.

        I'm inclined to do the latter, and actually considering to split functionality to more methods. Something like p__ret to get the return value, p__do for passing a closure, p__wrap for a wrapped aggregate object and p__wrapdo for a closure that takes a wrapped aggregate object seems to make sense.

        Makeshifts last the longest.

Re: RFC: Class::Proxy::MethodChain
by adrianh (Chancellor) on Feb 22, 2003 at 14:00 UTC

    Obviously if it works for you - go for it ;-)

    However, personally, I don't like it and find it the examples you've shown more opaque than the originals.

    I understand what you're trying to do, but I would personally use for where I can and live with the API where I cannot. For me the various calls to MethodChain obscure more than they clarify.

    Sorry :-/

      My beef with for (which I like a lot) in this case is that it necessitates weaving calls against different objects into a single block..

      I can see why people are saying the syntax C::P::MC produces looks more opaque. I think it's a matter of getting used to, and one of style.

      I was discussing this with Juerd yesterday. He made the point that once you set up a method to return $self natively, you can never again change that method to return something else should the need arise in future. That sounded like a very good argument to me, and I countered that intentionally outsourcing method chaining to something like C::P::MC instead of doing it natively would actually allow anyone to choose which style they prefer, for any class, at any one moment, to which he agreed. We also agreed that it's mostly a matter of style and taste - he said chaining didn't really appeal to him.

      So I guess this is really a matter of just going ahead and waiting for feedback from people who (try to) use it.

      Makeshifts last the longest.

        I basically agree :-) There are certainly other useful styles for setters. For example, the idiom of returning the old value in a setter allows you to compact:

        my $old = $foo->fribble $foo->fribble($new); ... do something ... $foo->fribble($old);

        to

        my $old = $foo->fribble($new) ... do something; $foo->fribble($old);

        However, for me the extra clutter that C::P::MC adds takes away the advantages that chaining gives you :-)

        That said - how about this as a way to get around the name clashing problems:

        package Class::Chain; use strict; use warnings; sub AUTOLOAD { my $self = shift; our $AUTOLOAD; my ($method) = ($AUTOLOAD =~ m/^.*::(.*)$/); return bless \$self->$method(@_) unless ref($self); return $$self->$1(@_) if $method =~ m/^raw_(.*)$/; return bless \$$self->$1(@_) if $method =~ m/^wrap_(.*)$/; $$self->$method(@_); return($self); }; sub chain { my ($self, $sub) = @_; $sub->($self); $self; }; sub DESTROY {};

        Which gives you:

        # call the whatever method and return $wrapped $wrapped->whatever # call the whatever method and return any value(s) $wrapped->raw_whatever # call the underlying method and wrap return value $wrapped->wrap_whatever # run $wrapped->$coderef and return $wrapped $wrapped->chain($coderef)

        So your second example would become something like (untested):

        my $file_dialog = Gtk::FileSelection ->Class::Chain::new("File Selection Demo") ->chain(sub {shift->wrap_ok_button ->label("Load") ->relief("half") ->width(80) ->height(50) ->signal_connect(clicked => sub { print $file_dialog->get_filename()->ret_val, "\n"; }); }) ->chain(sub {shift->wrap_cancel_button ->label("Exit") ->relief("half") ->width(80) ->height(50) ->signal_connect(clicked => sub{Gtk->main_quit}); }) ->set_filename("penguin.png") ->signal_connect(destroy => sub{Gtk->main_quit}) ->show();

        I still think it's ugly tho :-) :-)

Re: RFC: Class::Proxy::MethodChain
by runrig (Abbot) on Feb 25, 2003 at 00:04 UTC
    I think you will find some agreement on accessors and mutators that mutators should return the mutated object, and to quote the referenced post "They shall not return the old value, if you wanted to know that you should have asked." (Disclaimer, these are not necessarily my opinions, just other views which may support Aristotle's proposal).

    Personally, I think the chained method calls are more elegant, (e.g. ala File::Find::Rule), but I'll decide later when the need arises if its worthwhile enough to wrap objects in your C::P::MC module :-)

    Update: Here's my idea of how a very simplified C::P::MC might work:

Re: RFC: Class::Proxy::MethodChain
by Aristotle (Chancellor) on Feb 25, 2003 at 23:07 UTC
    Sometimes it's amazing how simple things can really turn out to be. At Re^5: RFC: Class::Proxy::MethodChain I said
    [H]ow I long for the beauty of saying something like
    given(Gtk::FileDialog->new()) { .ok_button.label("Load"); .cancel_button.label("Exit"); .connect_signal(destroy => sub { ... }); # ... }
    Then I went on to abuse map to almost get there. Well, guess what - I can emulate the exact same thing (modulo syntax) in three lines Perl 5 that could hardly be simpler:
    sub for_obj( local $_ = shift; shift->(); $_; }
    which lets me write
    my $window = for_obj(Gtk::Window->new("toplevel"), sub { $_->signal_connect(delete => sub { Gtk->exit(0) }); $_->set_title("Test"); $_->border_width(15); $_->add(for_obj(Gtk::Button->new("Quit") ,sub { $_->signal_connect(clicked => sub { Gtk->exit(0) }); $_->show; }); $_->show; });

    It can be that simple. Even the snippet at multiple method calls against the same object (f.ex GUI programming) is a dozen times more effort than necessary. (I added an update there pointing here.)

    I really need to get a handle on my hubris..

    Update: or simply

    sub for_obj { $_[1]->() for $_[0]; $_[0] }

    Makeshifts last the longest.

      Personal taste - but I'd have the sub first to keep it like map, grep, et al.

      sub apply_chain (&$) { local $_ = $_[1]; $_[0]->(); $_; } my $window = apply_chain { $_->signal_connect(delete => sub { Gtk->exit(0) }); $_->set_title("Test"); $_->border_width(15); $_->add( apply_chain { $_->signal_connect(clicked => sub { Gtk->exit(0) }); $_->show; } Gtk::Button->new("Quit") ); $_->show; } Gtk::Window->new("toplevel");
        The point of writing that function was that using map makes it read out of order - the new is at the bottom. Reversing the arguments on for_obj makes it practically the same thing as map so I would just stick with that one then.

        Makeshifts last the longest.

      This is much better. :-)

      -sauoq
      "My two cents aren't worth a dime.";
      

      my $window = for_obj(Gtk::Window->new("toplevel"), sub { $_->signal_connect(delete => sub { Gtk->exit(0) }); $_->set_title("Test"); $_->border_width(15); $_->add(for_obj(Gtk::Button->new("Quit") ,sub { $_->signal_connect(clicked => sub { Gtk->exit(0) }); $_->show; $_ }); $_->show; });

      I think I like explicit stuff better:

      my $window = do { local $_ = Gtk::Window->new('toplevel'); $_->signal_connect(delete => sub { Gtk->exit(0) }); $_->set_title('Test'); $_->border_width(15); $_->add( do { local $_ = Gtk::Button->new('Quit'); $_->signal_connect(clicked => sub { Gtk->exit(0) }); $_->show; $_; } ); $_->show; $_; };
      Sure, it takes more lines, but imho it's less messy, and much clearer. By the way - why is there a lone $_ in your inner for_obj?

      Juerd
      - http://juerd.nl/
      - spamcollector_perlmonks@juerd.nl (do not use).
      

        Because I had forgotten to convert that block from map syntax at first; when I did, I forgot to remove the $_. Fixed.

        Makeshifts last the longest.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://237243]
Approved by valdez
Front-paged by mojotoad
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others wandering the Monastery: (7)
As of 2014-07-14 07:07 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    When choosing user names for websites, I prefer to use:








    Results (255 votes), past polls