Top Banner
Object::Trampoline  Why having not the object you want is just what you need. Steven Lembark Workhorse Computing [email protected]
35

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

Jul 16, 2015

Download

Technology

Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Object Trampoline: Why having not the object you want is what you need.

Object::Trampoline

 Why having not the object you want is just what you need.

Steven LembarkWorkhorse [email protected]

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

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.

, 08/19/2013
The problem here is initializing objects before they are actually used for anything. Construction is all you need for method dispatch, but most languages (and programmers) initialize everything as it is constructed “just to be sure” that the objects are in a usable state.
Page 3: Object Trampoline: Why having not the object you want is what you need.

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.

, 08/19/2013
Another way to look at it is that “patterns” are meta-data about applying the language to some variety of tasks. Patterns describe how the language is applied, not what it looks like. So, C has patterns for pointer management; Java and Perl don't because they entirely lack pointers; string handling in Perl isn't much of a pattern; getting the same results in C is a lot more work.Perl's OO patterns are about choosing which way to do “it” and applying the proper tools.
Page 4: Object Trampoline: Why having not the object you want is what you need.

Pattern: “Lazy Initialization”

Separate “construction” and “initialization”.

You only need to construct an object to dispatch with it.

Delay initialization until methods are called.

, 08/19/2013
Lazy initialization in most languages is a pattern because it is difficult to do. There isn't much of a pattern for this in Perl because the language makes it so easy: just make separate “initalize” and “construct” subs and call them from “new” [whatever].You can even have a “new” in the base class that calls one constructor and EVERY::LAST::initialize using NEXT.
Page 5: Object Trampoline: Why having not the object you want is what you need.

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.

, 08/19/2013
The first way most of us hit on lazy init's is with a method call or just hacking the object. One is expensive, the other violates encapsulation; both of them break if the standard for “initialized” changes without the checks being updated. Think of cases where adding a new field to the object requires checking add'l parts of it.Sins of omission are also expensive: they end up creating order-of-execution errorrs.
Page 6: Object Trampoline: Why having not the object you want is what you need.

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.

, 08/19/2013
After getting bit by the first few omitted if-blocks, another common approach is usingd or factory objects throughout the code. This avoids overly-eager initialization but prevents keeping any useful state between calls; it is also painfully expensive in most cases.Using cached connections also works (a'la connect_cached or prepare_cached). These get expensive also due to computing hash keys for bulky items, or converting ref's to text in order to produce keys from them.There has to be a better way...
Page 7: Object Trampoline: Why having not the object you want is what you need.

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.

, 08/19/2013
If you are writing your own class then making it trampoline is fairly simple: Just add a “bounce” class and start your objects there. For example, Foo::Bar::Bounce can initialize the object, bless it back into Foo::Bar, and re-dispatch the current call with goto.This completely avoids any logic on the object contents: it starts life in the “initialize me” state and only goes through once; no factory class is needed since the object always passes through the bounce class once, and has minimal overhead doing it.This works in Perl – and probably Python – because the object's class can be modified over its lifetime.
Page 8: Object Trampoline: Why having not the object you want is what you need.

Warning:Warning:

Code shown here containsCode shown here containsgraphicgraphic AUTOLOAD's, AUTOLOAD's,un­filtered bless,un­filtered bless,& hard­wired stack.& hard­wired stack.

Parenthetical Parenthetical discretiondiscretion is advised. is advised.

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

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 newsub new{{

my $obj = my $obj = bless &construct,bless &construct, ' 'Foo::BounceFoo::Bounce';';

$initz{ refaddr $obj } = [ @_ ];$initz{ refaddr $obj } = [ @_ ];

$obj$obj}}

, 08/19/2013
Note that calling “&construct” pulls the prototype off of the stack, leaving the remaining arguments for initialize. Used with NEXT this gives a base class the ability to get a standard structure, with new calling EVERY::LAST::initialize.
Page 10: Object Trampoline: Why having not the object you want is what you need.

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 ) }}}

, 08/19/2013
The Bounce class is just an AUTOLOAD. Its only purpose in life is to intercept the method call, initialize the object, and push it back into the “real” method with an initialized object.This is where statically-typed languages fall on their faces, since the object's type cannot be modified. In those cases you are stuck with if-logic or always using factorys.
Page 11: Object Trampoline: Why having not the object you want is what you need.

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.

, 08/19/2013
Catch: Most of the time we use other peoples' code and don't have the luxury of re-writing it from scratch or updating it to add co-operating classes. This is we usually have dealing with modules: finding ways to use what is there without being able to modify it.One fix is adding wrapper classes, but even that is always possible.
Page 12: Object Trampoline: Why having not the object you want is what you need.

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.

, 08/19/2013
File descriptors were one of the original examples: replacing a bulky struct with the integer offset into an array. This makes duplicating them trivial: just pass out the same integer.
Page 13: Object Trampoline: Why having not the object you want is what you need.

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.

, 08/19/2013
The point of this is to encapsulate any logic necessary to deal with the re-dispatch into the object. Once configured, the actions Just Happen without additional logic in code using the object.
Page 14: Object Trampoline: Why having not the object you want is what you need.

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.

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

Click to add code

The “Foo::Bar” class...

my $obj = my $obj = Foo::BarFoo::Bar->foobar->foobar((

qw( bim bam )qw( bim bam )););

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

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

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

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.

, 08/19/2013
Homework: How many “rules” in PBP does this module break?Few examples: Using AUTOLOAD at all, hardwired classes in bless.That doesn't make this approach “bad”, it just means that the PBP approaches are supposed to be a starting point for things you should think about carefully before using.
Page 18: Object Trampoline: Why having not the object you want is what you need.

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''

}}

, 08/19/2013
And here is the magic: A subref delays performing its action until it is called. At that point, it calls the constructor.Once the object has passed through here once, O::T is “consumed”. The remaining work is handled by O::T::B.
Page 19: Object Trampoline: Why having not the object you want is what you need.

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.

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

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 {}

, 08/19/2013
Recall that the Perl function call stack is a list of references. This is usually not an issue; here it is necessary to make the algorithm work. Replacing $_[0] updates the object in place, after which it never comes back to O::T::B.DESTROY here avoids creating the object during destruction, which has any number of nasty side effects in global destruction, which is done out-of-order.
Page 21: Object Trampoline: Why having not the object you want is what you need.

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.

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

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 };};}}

, 08/19/2013
Another nice thing about Perl is that its symbol table is exposed. In this case I need to make sure that I override everything in UNIVERSAL's space. This really has to check whether the symbols have CODE ref's in them since it only has to override methods, but that doesn't fit into the slide.
Page 23: Object Trampoline: Why having not the object you want is what you need.

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.

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

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''}}

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

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.

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

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.

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

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.

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

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.

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

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.

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

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

}}

, 08/19/2013
Once again, sanity checks on the relevance of names passed in are ignored in order to fit the code onto one slide.Adding checks for exists $handlz{ $name } are simple enough.
Page 31: Object Trampoline: Why having not the object you want is what you need.

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.

, 08/19/2013
Gads I hate getting told that a “file doesn't exist.” At least give the bloody path! Wrapping entire classes with sanity-checking code is doable, this is the simplest approach I've found – and permits dynamic checking of the necessary parameters.
Page 32: Object Trampoline: Why having not the object you want is what you need.

Finding sanity

packagepackage Sane::FooSane::Foosub 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'

}}

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

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.

, 08/19/2013
PID's are stored in all sorts of things, checking “$$ != $pid” before using an object can save all sorts of problems. If you need to check every time the object is used then look at Object::Wrapper. More often, a one-shot test is sufficient and O::T::B gives a lightweight solution.
Page 34: Object Trampoline: Why having not the object you want is what you need.

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.

, 08/19/2013
Such is the price of glory: Trampolines are not the object you want.Overloading the dereferencing operators doesn't work since O::T has no way of knowing what the object is supposed to be. The only thing that can be reliably handled is method dispatch, via AUTOLOAD. Tying the O::T::B object is doable, but gets pretty messy pretty quickly.The real fix is to just use encapsulated objects.
Page 35: Object Trampoline: Why having not the object you want is what you need.

References

On CPAN:

O::T is ~30 lines of code, 300 lines of POD & comments.

Alternative solutions:Data::Thunk, Scalar::Defer, Data::Lazy.

Questions?