[OT] Wrapping methods
Sorry to mention the 4 letter word... I'm not sure if there is a good answer to this, but: Say I have a system of interchangeable components defined by @ISA = 'Generic::Base::Class'; sub run { ... # does stuff } such that it's invoked to do its work as $object->run(...); Is there any not-insanely-hacky way to conjure up a wrapper class Generic::Base::ClassWithTimeout such that a component could be written as @ISA = 'Generic::Base::ClassWithTimeout'; sub run { ... # does stuff } called, as ever, as $object->run(...); but Generic::Base::ClassWithTimeout has done something funky to wrap &run in a routine that does the eval block/alarm/$SIG{ALARM} handler? Basically, the fun is that I'd like to do something equivalent to invert the order of inheritance, such that the parent's &run seems to be called first, and it can "SUPER" delegate to its child. Of course this can also be solved in various other ways, such as having the code be @ISA = 'Generic::Base::ClassWithTimeout'; sub run_with_timeout { ... # does stuff } and Generic::Base::ClassWithTimeout having a run() method that calls $self->run_with_timeout(...) inside the alarmed block, but that doesn't feel as elegant an interface. Nicholas Clark
Re: [OT] Wrapping methods
On Mon, Jul 06, 2009 at 05:06:03PM +0100, Nicholas Clark wrote: > but Generic::Base::ClassWithTimeout has done something funky to wrap &run > in a routine that does the eval block/alarm/$SIG{ALARM} handler? > Basically, the fun is that I'd like to do something equivalent to invert the > order of inheritance, such that the parent's &run seems to be called first, > and it can "SUPER" delegate to its child. You can't use inheritance for this. You need to detect whenever a class is loaded - perhaps by doing some Voodoo in @INC or %INC. Then to figure out which classes inherit from your class, use Class::CanBeA, and then Hook::LexWrap or something similar to wrap its run() method. It sounds like this would be terribly fragile though. eg, if run() is AUTOLOADed or otherwise defined at runtime. -- David Cantrell | London Perl Mongers Deputy Chief Heretic Hail Caesar! Those about to vi ^[ you!
Re: [OT] Wrapping methods
On 6 Jul 2009, at 17:06, Nicholas Clark wrote: such that it's invoked to do its work as $object->run(...); Is there any not-insanely-hacky way to conjure up a wrapper class Generic::Base::ClassWithTimeout such that a component could be written as @ISA = 'Generic::Base::ClassWithTimeout'; sub run { ... # does stuff } called, as ever, as $object->run(...); but Generic::Base::ClassWithTimeout has done something funky to wrap &run in a routine that does the eval block/alarm/$SIG{ALARM} handler? http://search.cpan.org/~drolsky/Moose/lib/Moose/Cookbook/Basics/Recipe6.pod Basically, the fun is that I'd like to do something equivalent to invert the order of inheritance, such that the parent's &run seems to be called first, and it can "SUPER" delegate to its child. That's exactly what the augment/inner feature of Moose provides, if I've understood you correctly. Of course you might not be ready to drink the Moose kool-aid and I dare say the list will come up with other ways, but this seems like a good option. Part of me says that inheritance is not the correct way to model this, at all. Yet I suspect you've got some methods which can hang, and where you want to inject from outside the ability to have those methods time out, without fiddling with the code of those classes. I don't know a good way to do what you asked, without significantly expanding the scope of the question or without knowing more about the actual problem domain. /joel
Re: [OT] Wrapping methods
On Jul 6, 2009, at 9:06, Nicholas Clark wrote: Of course this can also be solved in various other ways, such as having the code be @ISA = 'Generic::Base::ClassWithTimeout'; sub run_with_timeout { ... # does stuff } and Generic::Base::ClassWithTimeout having a run() method that calls $self->run_with_timeout(...) inside the alarmed block, but that doesn't feel as elegant an interface. I'm pretty sure Moose can help with this sort of thing. I think you can get the API reasonably elegant with just clever naming though. Don't make it "run_with_timeout", but "_run" (or whatever - something generic). The Generic::Base::run can call that automatically so it'll Just Work for all the classes (with or without wrappers). - ask
Re: [OT] Wrapping methods
Em Seg, 2009-07-06 às 17:06 +0100, Nicholas Clark escreveu: > Sorry to mention the 4 letter word... > I'm not sure if there is a good answer to this, but: > Say I have a system of interchangeable components defined by >@ISA = 'Generic::Base::Class'; >sub run { ... } > such that it's invoked to do its work as $object->run(...); > Is there any not-insanely-hacky way to conjure up a wrapper class > Generic::Base::ClassWithTimeout > such that a component could be written as >@ISA = 'Generic::Base::ClassWithTimeout'; >sub run { ... } > called, as ever, as $object->run(...); As it was already pointed out, Moose provides you the tools to do that, but the most sane way to do that is by using a metaclass, that will allow you to use method modifiers, as you can see in add_around_method_modifier. http://search.cpan.org/~drolsky/Class-MOP-0.89/lib/Class/MOP/Class.pm#Method_Modifiers daniel
Re: [OT] Wrapping methods
On Mon, Jul 6, 2009 at 2:24 PM, Joel Bernstein wrote: >> Basically, the fun is that I'd like to do something equivalent to invert >> the >> order of inheritance, such that the parent's &run seems to be called >> first, >> and it can "SUPER" delegate to its child. > > That's exactly what the augment/inner feature of Moose provides, if I've > understood you correctly. For an alarm I'd say a "before" modifier would be better than a wrapper of any sort. -- Eden Cardim Need help with your Catalyst or DBIx::Class project? Code Monkeyhttp://www.shadowcat.co.uk/catalyst/ Shadowcat Systems Ltd. Want a managed development or deployment platform? http://edenc.vox.com/http://www.shadowcat.co.uk/servers/
Re: [OT] Wrapping methods
David Cantrell wrote: > On Mon, Jul 06, 2009 at 05:06:03PM +0100, Nicholas Clark wrote: > >> but Generic::Base::ClassWithTimeout has done something funky to >> wrap &run in a routine that does the eval block/alarm/$SIG{ALARM} >> handler? Basically, the fun is that I'd like to do something >> equivalent to invert the order of inheritance, such that the >> parent's &run seems to be called first, and it can "SUPER" delegate >> to its child. > > You can't use inheritance for this. You need to detect whenever a > class is loaded - perhaps by doing some Voodoo in @INC or %INC. Then > to figure out which classes inherit from your class, use > Class::CanBeA, and then Hook::LexWrap or something similar to wrap > its run() method. I second that. > It sounds like this would be terribly fragile though. eg, if run() > is AUTOLOADed or otherwise defined at runtime. Good practices dictate that whenever you provide AUTOLOADed methods, you should also override UNIVERSAL::can() to provide proper information about the AUTOLOADable methods. If you have reasonable control over the implementation, I would dare to contradict David and state that fragility is not a big deal. My €0.02 -- Luis Motta Campos is a software engineer, Perl Programmer, foodie and photographer.
Re: [OT] Wrapping methods
Nicholas Clark wrote: Of course this can also be solved in various other ways, [...] but that doesn't feel as elegant an interface. Perhaps not, but IMHO having two distinct methods is the Right Way To Do It. Anything else runs the risk of being Too Clever By Far[1]. I would have an external run() method to act as an interface and an internal _run() method to provide the implementation. sub run { # setup $self->_run(@_); # cleanup } That's the simple, vanilla OO approach. You can tart it up with a bit of Moose magic if you like, but that should be the dressing on top, not the cake itself. That said, I think a better approach in this situation would be to implement a generic call_method_with_timeout() method/function that you can inherit from a base class, import from an external module, or mixin using some other fancy technique. sub call_method_with_timeout { my ($self, $method, @args) = @_; # setup # your timeout code $self->$method(@args); # goes around this # cleanup # bit here } You can then implement run() as a simple call to the above method. use Your::Utils 'call_method_with_timeout'; sub run { # easily skimmable, self documenting code, yummy. shift->call_method_with_timeout( _run => @_ ); } If a slick API is your thing then another approach is to use a monad object to represent the timeout wrapper. Something like this perhaps: package Method::Timeout; our $AUTOLOAD; sub _bind_ { my ($class, $object) = @_; bless \$object, ref $class || $class; } sub AUTOLOAD { my $self = shift; my ($name) = ($AUTOLOAD =~ /([^:]+)$/ ); return if $name eq 'DESTROY'; # setup # your timeout code my $result = $$self->$name(@_); # goes around this # cleanup# bit here return $result; } Then in your object class (or imported from another module) use Method::Timeout; sub timeout { Method::Timeout->_bind_(@_); } The timeout() method returns a Method::Timeout object wrapped around your $self object. This wrapper object then delegates all method calls to the original object, but with the timeout alarm set. The end result is that you can then call any method with a timeout wrapper like so: $object->timeout->run(@args); You can also pass additional parameters to the timeout method if you like, e.g. $object->timeout(10)->run(@args); You can also reuse the timeout object if you need to: my $timeout = $object->timeout(10); $timeout->foo(); $timeout->bar(); $timeout->baz(); And it also works with class methods (as does call_method_with_timeout()): my $object = My::Class->timeout->new( connect => $a_slow_server ); I find this approach rather satisfying. It clearly separates "the method that you're calling" from "the way you want the method to be called". Separation of concerns and all that. Badger::Base implements the try() method this way. It simply puts an eval { } wrapper around the method you're calling, effectively downgrading a thrown exception to an undefined return value [2]. if ($object->try->some_method(@args)) { print "success\n!"; } elsif ($@) { print "failed: ", $object->error, "\n"; } else { print "declined: method returned false value, no error thrown\n"; } It's not as elegant as proper try/catch blocks, but I think it's nicer than the Old Skool approach of wrapping eval { } blocks around the method call. if (eval { $object->method(@args) }) { # Not as nice, IMHO ... } But I digress... Whichever way you do it, I think it's important to separate the underlying action being performed (the _run() method or whatever you call it) from the additional timeout behaviour that is orthogonal to it. Cheers A [1] i.e. clever enough to confuse others [2] Returning undef to indicate failure is a Bad Thing. Have your methods throw exceptions by default and let the caller make the decision to ignore/handle them (using try(), eval { } or whatever) when appropriate. (for the grandmas who don't already know how to suck eggs :-)