Object Trampoline: Why having not the object you want is what you need.


Published on

Overview of Trampoline Objects in Perl with examples for lazy construction, lazy module use, added sanity checks. This version includes corrections from the original presented at OSCON 2013 and comments.

Published in: Technology
1 Like
  • Be the first to comment

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

Object Trampoline: Why having not the object you want is what you need.

  1. 1. Object::Trampoline  Why having not the object you want  is just what you need. Steven Lembark Workhorse Computing lembark@wrkhors.com
  2. 2. We've all been there Starting the web server crashes your database. Testing requires working handles for services you don't test. Compiling error messages in 15 languages you never use.
  3. 3. Patterns: Ways to Scratch an Itch Language-specific, idiomatic solutions. Meta-data about language strengths, limitations. Example: C patterns are about pointers and string handling. Perl patterns here: objects, dispatch, and stack manglement.
  4. 4. Pattern: “Lazy Initialization” Separate “construction” and “initialization”. You only need to construct an object to dispatch with it. Delay initialization until methods are called.
  5. 5. Common way: If-logic Construct a naked object. Check object contents within every method, overload, ... Works until someone forgets to check... … or benchmarks the overhead of checking.
  6. 6. Other ways: Use a factory class to construct a new object every time. Hard to maintain state across calls. Use a cached connection (e.g., DBI::connect_cached). Expensive: check what you know to be true every time. Not lazy or impatient.
  7. 7. Pattern: Trampoline Class Simplest approach: co-operating classes with re-bless. Construct the object in one class. Bless it into the “bounce class”. Bounce class initializes the object. Re-bless the object where it belongs.
  8. 8. Warning:Warning: Code shown here containsCode shown here contains graphicgraphic AUTOLOAD's, AUTOLOAD's, un­filtered bless,un­filtered bless, & hard­wired stack.& hard­wired stack. Parenthetical Parenthetical discretiondiscretion is advised. is advised.
  9. 9. Example: Multi-stage initialization package Foo;package Foo; ## new calls the constructor, re-blesses thenew calls the constructor, re-blesses the ## object then stores the initialize data.object then stores the initialize data. sub newsub new {{ my $obj =my $obj = bless &construct,bless &construct, ''Foo::BounceFoo::Bounce';'; $initz{ refaddr $obj } = [ @_ ];$initz{ refaddr $obj } = [ @_ ]; $obj$obj }}
  10. 10. Example: Multi-stage initialization package Foo::Bounce;package Foo::Bounce; AUTOLOADAUTOLOAD {{ ...... ## reset the class, lazy initializereset the class, lazy initialize bless $obj, 'Foo';bless $obj, 'Foo'; my $argzmy $argz = delete $initz{ refaddr $obj };= delete $initz{ refaddr $obj }; $obj->initialize( @$argz );$obj->initialize( @$argz ); goto &{ $object->can( $method ) }goto &{ $object->can( $method ) } }}
  11. 11. Problem: Co-operating classes require classes. Hard to add after the fact. What if you can't re-write the class? Wrapping every single class is not an option. A generic solution is the only reasonable fix.
  12. 12. Another Pattern: “Flyweight Object” Cheap to construct. Stand-in in for a bulky, expensive object. Example: Surrogate keys, Unix file descriptors. Like references or pointers, managed by user code.
  13. 13. Flyweight: Trampoline Object Replaces itself with another object. Temporary stand-in for the “real” object. Only behavior: creating a new object and re-dispatching. Pass trough the class/object once.
  14. 14. How is it done? Quite easily: AUTOLOAD's can intercept method calls cleanly. References modify the object in place. goto &sub replaces a call on the stack. Result: Method re-dispatched with a newly-minted object. No if-logic, no re-construction, no key-magic.
  15. 15. Click to add code The “Foo::Bar” class... my $obj =my $obj = Foo::BarFoo::Bar->foobar->foobar (( qw( bim bam )qw( bim bam ) ););
  16. 16. The “Foo::Bar” class... … becomes an argument. my $obj =my $obj = Foo::BarFoo::Bar->frob->frob (( qw( bim bam )qw( bim bam ) );); my $obj =my $obj = Object::TrampolineObject::Trampoline->frob->frob (( qw(qw( Foo::BarFoo::Bar bim bam )bim bam ) );); Click to add code
  17. 17. What you knead Object::Trampoline is nothing but an AUTOLOAD. Blesses a subref into Object::Trampoline::Bounce. Yes, Virginia, Perl can bless something other than a hash.
  18. 18. Construct a bouncing object package Object::Trampoline;package Object::Trampoline; AUTOLOADAUTOLOAD {{ shift;shift; my ( $protomy ( $proto, @argz ) = @_;, @argz ) = @_; my $methodmy $method = ( split /::/, $AUTOLOAD )[ -1 ];= ( split /::/, $AUTOLOAD )[ -1 ]; my $objmy $obj == sub { $proto->$method( @argz ) }sub { $proto->$method( @argz ) };; bless $obj, 'bless $obj, 'Object::Trampoline::BounceObject::Trampoline::Bounce'' }}
  19. 19. Bouncing the object Call the constructor: $_[0] = $_[0]->(); At this point it is no longer a Trampoline object. AUTOLOAD deals with the method call. Needs a stub DESTROY to avoid creating the object.
  20. 20. And, Viola!, the object you wanted. AUTOLOADAUTOLOAD {{ ## assign $_[0] replaces caller's object.assign $_[0] replaces caller's object. $_[0] = $_[0]->();$_[0] = $_[0]->(); my $class = blessed $_[0];my $class = blessed $_[0]; my $method = ( split /::/, $AUTOLOAD )[ -1 ];my $method = ( split /::/, $AUTOLOAD )[ -1 ]; local $a = $class->can( $method ) ) and goto &$a;local $a = $class->can( $method ) ) and goto &$a; my $obj = shift;my $obj = shift; $obj->$method( @_ )$obj->$method( @_ ) }} DESTROY {}DESTROY {}
  21. 21. Override UNIVERSAL All classes inherit DOES, VERSION, isa, can. This means that the AUTOLOAD will not intercept them. Fix: Overload UNIVERSAL methods to use AUTOLOAD.
  22. 22. Override UNIVERSAL for my $name ( keys %{for my $name ( keys %{ $::{ 'UNIVERSAL::' }$::{ 'UNIVERSAL::' } } )} ) {{ *{ qualify_to_ref $name }*{ qualify_to_ref $name } = sub= sub {{ $AUTOLOAD = $name;$AUTOLOAD = $name; goto &AUTOLOADgoto &AUTOLOAD };}; }}
  23. 23. Extra-Bouncy: Object::Trampoline::Use There are times when using the module is important. Object::Trampoline::Use does a string eval. Pushes a “use $package” into the caller's class. Accommodates side-effects of import in the correct module.
  24. 24. Lazy “use” AUTOLOADAUTOLOAD {{ ...... my $sub =my $sub = subsub {{ eval "package $caller; use $class” or croak ...or croak ... $class->$method( @argz )$class->$method( @argz ) };}; bless $sub, 'bless $sub, 'Object::Trampoline::BounceObject::Trampoline::Bounce'' }}
  25. 25. Hence a subref: Object::Trampoline::Bounce has no idea what the sub does. Encapsulation gives a better division of labor: One class knows what gets done, O::T::B does it reliably. Any class that wants to can use O::T::B.
  26. 26. Feeding the Industrial Revolution Shifted off the stack, lexical copies are replaced in-place. Trivial to use Trampolines as factory classes. Annoying if you don't plan correctly.
  27. 27. Shifting without factories Data::Alias simplifies shifting objects off the stack: alias my $obj = shift; The “alias” leaves $obj as a reference to the original. After that trampolines will do the right thing.
  28. 28. Example: Dealing with handles. O::T originally developed for a call-center system. DBI to query pending trouble tickets. Asterisk to dial out to the group assigned to handle a ticket. SIP to originate the calls. Berkeley DB handle to query Asterisk status. Testing complicated by unnecessary servers. Fix was trampolines.
  29. 29. Sharing is caring The server handles were installed from a common module. Singleton handles update in place on use. Testing only requires the handles actually exercised.
  30. 30. Sharing a handle my %handlz =my %handlz = (( dbhdbh => Object::Trampoline->new( @dbi_argz ),=> Object::Trampoline->new( @dbi_argz ), sip_hsip_h => Object::Trampoline->new( @sip_argz ),=> Object::Trampoline->new( @sip_argz ), ...... );); sub importsub import {{ my $callermy $caller = caller;= caller; for my $name ( @_ )for my $name ( @_ ) {{ *{ qualify_to_ref $name, $caller }*{ qualify_to_ref $name, $caller } = $handlz{ $name };= $handlz{ $name }; }} returnreturn }}
  31. 31. Better Errors Bad Error: “Error: File not found.” Also Bad: “Error: xyz.config not found” Fix: pre-check the files, reporting the working directory. Add error messages with full paths, failure reasons. Fix: codref blessed into O::T::Bounce.
  32. 32. Finding sanity packagepackage Sane::FooSane::Foo sub newsub new {{ my $pathmy $path = shift;= shift; bless subbless sub {{ my $cwdmy $cwd = getcwd;= getcwd; -e $path-e $path or die "Non-existant: '$path' ($cwd)";or die "Non-existant: '$path' ($cwd)"; -r _-r _ or die "Non-readable: '$path' ($cwd)";or die "Non-readable: '$path' ($cwd)"; ...... Foo->new( $path )Foo->new( $path ) },}, 'Object::Trampoline::Bounce''Object::Trampoline::Bounce' }}
  33. 33. Automatic checking Sane::Foo flyweight does the checking automatically. Object::Trampoline::Bounce executes the object. No outside data required: Just the closure. Returns a Foo object after pre-check. Trivial to add $$ check for crossing process boundarys.
  34. 34. Sufficiently developed technology... Misguided magic: Anyone who violates encapsulation gets what they deserve... … or the objects have to be tied and overloaded. Accidental factory objects. Relying too early on import side-effects w/ O::T::Use.
  35. 35. References On CPAN: O::T is ~30 lines of code, 300 lines of POD & comments. Alternative solutions: Data::Thunk, Scalar::Defer, Data::Lazy. Questions?