Jonathan Rockway throws down the gauntlet on Object::Tiny

Alias on 2007-09-04T02:52:13

In what I'm sure he meant to be a throwaway rhetorical question, Jonathan asks:

> Why bother when Class::Accessor::* already does the same thing?

OH IT'S SO ON! :)

So after writing some quick benchmarks (in the examples directory of the new Object::Tiny 1.03) and doing some investigations and tweaking (stole a small trick from Class::Accessor::Fast to make my accessors slightly faster), here is the answer to Jonathan's question, from the Object::Tiny docs.

------------------------------------------------------------------------------

Why bother when Class::Accessor::* already does the same thing?

As a class builder, Object::Tiny inevitably is compared to Class::Accessor and related modules. They seem so similar, so why would I reimplement it?

The answer is that for experienced developers that don't need or want hand-holding, Object::Tiny is just outright better, faster or cheaper on every single metric than Class::Accessor::Fast, which is the most comparable member of the Class::Accessor::* family.

Object::Tiny is 93% smaller than Class::Accessor::Fast

Class::Accessor::Fast requires about 125k of memory to load.

Object::Tiny requires about 8k of memory to load.

Object::Tiny is 75% more terse to use than Class::Accessor::Fast

Using Object::Tiny requires the least possible number of keystrokes (short of making the actual name Object::Tiny smaller).

And it requires no ugly constructor methods.

I mean really, what sort of a method name is 'mk_ro_accessors'. That sort of thing went out of style in the early nineties.

Using Class::Accessor::Fast...

  package Foo::Bar;
  use base 'Class::Accessor::Fast';
  Foo::Bar->mk_ro_accessors(qw{ foo bar baz });
Using Object::Tiny...
  package Foo::Bar;
  use Object::Tiny qw{ foo bar baz };
You might note I've been a little generous there by reusing the class for the method call (which is how the SYNOPSIS lists it). The alternative commonly used by a lot of people, to avoid Repeating Yourself (which is one of those things you Don't do) is to use the longer (in this case) and even uglier __PACKAGE__ compiler flag.

Further, Object::Tiny lets you pass your params in directly, without having to wrap them in an additional HASH reference that will just be copied ANYWAY inside the constructor.

Using Class::Accessor::Fast...
  my $object = Foo::Bar->new( {
      foo => 1,
      bar => 2,
      baz => 3,
  } );
Using Object::Tiny...
  my $object = Foo::Bar->new(
      foo => 1,
      bar => 2,
      baz => 3,
  );
Object::Tiny constructors are 110% faster than Class::Accessor::Fast

Object::Tiny accessors are identical in speed to Class::Accessor::Fast accessors, but Object::Tiny constructors are TWICE as fast as Class::Accessor::Fast constructors, DESPITE C:A:Fast forcing you to pass by reference (which is typically done for speed reasons).
  Benchmarking constructor plus accessors...
               Rate accessor     tiny
  accessor 100949/s       --     -45%
  tiny     182382/s      81%       --
  
  Benchmarking constructor alone...
               Rate accessor     tiny
  accessor 156470/s       --     -54%
  tiny     342231/s     119%       --
  
  Benchmarking accessors alone...
             Rate     tiny accessor
  tiny     81.0/s       --      -0%
  accessor 81.0/s       0%       --
Object::Tiny pollutes your API 95% less than Class::Accessor::Fast

Object::Tiny adds two methods to your class, new and import. The new constructor is so trivial you can just ignore it and use your own if you wish, and the import call will shortcut and do nothing (it is used to implement the "use Object::Tiny qw{ foo bar baz };" syntax itself).

So if you make your own versions of either of the two methods, you can ignore the Object::Tiny one.

Class::Accessor::Fast isn't quite as light, adding all sorts of useless extra methods. Worse, it adds man of them as public methods!!!

Why on earth would you want to allow your users to add more accessors to the classes they are using at run-time?

Here's what the classes used in the benchmark end up like.
    DB<1> use Class::Inspector
  
    DB<2> x Class::Inspector->methods('Foo_Bar_Tiny');
  0  ARRAY(0xfda780)
     0  'bar'
     1  'baz'
     2  'foo'
     3  'import'
     4  'new'
  
    DB<3> x Class::Inspector->methods('Foo_Bar_Accessor');
  0  ARRAY(0xfdb3c8)
     0  '_bar_accessor'
     1  '_baz_accessor'
     2  '_carp'
     3  '_croak'
     4  '_foo_accessor'
     5  '_mk_accessors'
     6  'accessor_name_for'
     7  'bar'
     8  'baz'
     9  'best_practice_accessor_name_for'
     10  'best_practice_mutator_name_for'
     11  'follow_best_practice'
     12  'foo'
     13  'get'
     14  'make_accessor'
     15  'make_ro_accessor'
     16  'make_wo_accessor'
     17  'mk_accessors'
     18  'mk_ro_accessors'
     19  'mk_wo_accessors'
     20  'mutator_name_for'
     21  'new'
     22  'set'
Object::Tiny adds 2 extra methods to your class.

Class::Accessor adds 16 extra methods, plus one more for every accessor.

Object::Tiny doesn't have the caveats of Class::Accessor::Fast

When you call use Object::Tiny qw{ foo bar baz } it isn't treated as some sort of complete specification for the class, it's just a list of accessors you want made for you.

So if you want to customize foo you don't need to get into contortions with "pure" base classes or calling alternate internal methods. Just make your own foo method and remove foo from the list passed to the use call.

Object::Tiny is more back-compatible than Class::Accessor::Fast

Class::Accessor::Fast has a minimum Perl dependency of 5.005002.

Object::Tiny has a minimum Perl dependency of 5.004.

Object::Tiny has no module dependencies whatsoever

Object::Tiny does not load ANYTHING at all outside of it's own single .pm file.

So Object::Tiny will never get confused in odd situations due to old or weird versions of other modules (Class::Accessor::Fast has a dependency on base.pm, which has some caveats of it's own).

--------------------------------------------------------------------

So yes, Object::Tiny kicks Class::Accessor's ass all over the interpreter.

Now personally, the question I think is more relvant is...

> If all you want is a bunch of simple accessors, why use a helper class at all?

The answer for me is convenience. And that's why Object::Tiny exists, for times when I'm lazy and in a hurry, and I just want to press less buttons to get the same thing as I would have typed anyway.

And for me, Class::Accessor doesn't deliver on that need.