Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Object::Trampoline -- When having not the objec...

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

Trampoline objects provide stand-ins for other objects, delaying
construction of the 'real' object until a method is called. This
saves both the overhead and errors of initializing unnecessary or
large objects until they are used and if-logic to perpetually check
if the initialization has happened. They can be used for any objects,
but are most helpful for bulky or slow initialization that will
unnecessarly inhibit process startup.

This talk looks at implementing a Trampoline in Perl, how to use them,
and a few issues with using them.

As an exercsise: See how many 'rules' in Perl Best Prectices this
breaks, or how you might successfully implement these in any another
language.

Avatar for Steven Lembark

Steven Lembark PRO

June 01, 2013
Tweet

More Decks by Steven Lembark

Other Decks in Technology

Transcript

  1. Object::Trampoline Why having not the object you want is just

    what you need. Steven Lembark Workhorse Computing [email protected]
  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. 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. 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. 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. 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. 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. Warning: Warning: Code shown here contains Code shown here contains

    graphic graphic AUTOLOAD's, AUTOLOAD's, un-filtered bless, un-filtered bless, & hard-wired stack. & hard-wired stack. Parenthetical Parenthetical discretion discretion is advised. is advised.
  9. Example: Multi-stage initialization package Foo; package Foo; # # new

    calls the constructor, re-blesses the new calls the constructor, re-blesses the # # object then stores the initialize data. object then stores the initialize data. sub new sub new { { my $obj = my $obj = bless &construct, bless &construct, ' 'Foo::Bounce Foo::Bounce'; '; $initz{ refaddr $obj } = [ @_ ]; $initz{ refaddr $obj } = [ @_ ]; $obj $obj } }
  10. Example: Multi-stage initialization package Foo::Bounce; package Foo::Bounce; AUTOLOAD AUTOLOAD {

    { ... ... # # reset the class, lazy initialize reset the class, lazy initialize bless $obj, 'Foo'; bless $obj, 'Foo'; my $argz my $argz = delete $initz{ refaddr $obj }; = delete $initz{ refaddr $obj }; $obj->initialize( @$argz ); $obj->initialize( @$argz ); goto &{ $object->can( $method ) } goto &{ $object->can( $method ) } } }
  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. 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. 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. 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. Click to add code The “Foo::Bar” class... my $obj =

    my $obj = Foo::Bar Foo::Bar->foobar ->foobar ( ( qw( bim bam ) qw( bim bam ) ); );
  16. The “Foo::Bar” class... … becomes an argument. my $obj =

    my $obj = Foo::Bar Foo::Bar->frob ->frob ( ( qw( bim bam ) qw( bim bam ) ); ); my $obj = my $obj = Object::Trampoline Object::Trampoline->frob ->frob ( ( qw( qw( Foo::Bar Foo::Bar bim bam ) bim bam ) ); ); Click to add code
  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. Construct a bouncing object package Object::Trampoline; package Object::Trampoline; AUTOLOAD AUTOLOAD

    { { shift; shift; my ( $proto my ( $proto, @argz ) = @_; , @argz ) = @_; my $method my $method = ( split /::/, $AUTOLOAD )[ -1 ]; = ( split /::/, $AUTOLOAD )[ -1 ]; my $obj my $obj = = sub { $proto->$method( @argz ) } sub { $proto->$method( @argz ) }; ; bless $obj, ' bless $obj, 'Object::Trampoline::Bounce Object::Trampoline::Bounce' ' } }
  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. And, Viola!, the object you wanted. AUTOLOAD AUTOLOAD { {

    # # 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. 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. 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 &AUTOLOAD goto &AUTOLOAD }; }; } }
  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. Lazy “use” AUTOLOAD AUTOLOAD { { ... ... my $sub

    = my $sub = sub sub { { eval "package $caller; use $class” or croak ... or croak ... $class->$method( @argz ) $class->$method( @argz ) }; }; bless $sub, ' bless $sub, 'Object::Trampoline::Bounce Object::Trampoline::Bounce' ' } }
  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. 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. 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. 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. 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. Sharing a handle my %handlz = my %handlz = (

    ( dbh dbh => Object::Trampoline->new( @dbi_argz ), => Object::Trampoline->new( @dbi_argz ), sip_h sip_h => Object::Trampoline->new( @sip_argz ), => Object::Trampoline->new( @sip_argz ), ... ... ); ); sub import sub import { { my $caller my $caller = caller; = caller; for my $name ( @_ ) for my $name ( @_ ) { { *{ qualify_to_ref $name, $caller } *{ qualify_to_ref $name, $caller } = \$handlz{ $name }; = \$handlz{ $name }; } } return return } }
  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. Finding sanity package package Sane::Foo Sane::Foo sub new sub new

    { { my $path my $path = shift; = shift; bless sub bless sub { { my $cwd my $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. 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. 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. References On CPAN: O::T is ~30 lines of code, 300

    lines of POD & comments. Alternative solutions: Data::Thunk, Scalar::Defer, Data::Lazy. Questions?