Why does Object::Tiny only support getters

Alias on 2010-08-15T01:39:50

http://perlalchemy.blogspot.com/2010/08/objecttinyrw-and-moosexnonmoose.html

Zbigniew Lukasiak tries out Object::Tiny and wonders why it is that I didn't allow for the creation of setters when it is only a one line change.

Like most ::Tiny modules the reason is a bit complex and the result of compromises.

Object::Tiny began as an attempt to create a lighter, faster, version of Class::Accessor. A way to bulk-generate the accessor code I had to type over and over again.

However, where I differ is a strong preference for light and elegant API design.

And so I decided to implement mine with as little implementation code as possible, and as little API code as possible.

Once you have decided to go down the simplicity path, there's a couple of standard techniques you often end up using.

The first and most important is state reduction.

In their introduction to Erlang, the founders of that language describe state as one of the main sources of failures in programs. And so anything that removes state, at the very least unnecessary state, is a positive. Especially if the state reduction also results in code reduction, and a reduction in computation.

So take the following example, where we create an object with some attributes and then run some code that will use those object attributes..

my $object = Class->new;
$object->foo(1);
$object->bar(2);
$object->do_something;
This is a use case that we see fairly often, but it's really quite horrible code. It is really only the object-oriented equivalent of something like the following.
our $Object::foo = 1;
our $Object::bar = 2;
do_something('Object');
It is especially bad code if the following code would throw an exception.
my $object = Class->new;
$object->do_something;
If this blows up, then you are REALLY doing something wrong, because you have allowed the creation of completely invalid objects. Now anybody taking one of these objects as a parameters needs to do with following.
sub foo {
    my $object = shift;
    unless (
        $object->isa('Class')
        and
        defined $object->foo
        and
        $object->foo > 0
        and
        defined $object->bar
        and
        $object->bar > 2
    ) {
        die "Invalid object";
    }
}
If you are going to create an object for something, you HAVE to be sure that the objects are trustworthy.

And so you should never allow objects to exist that are invalid. EVERY object should be a valid object.

At the absolute minimum objects should be able to default every attribute to something reasonable and unlikely to cause problems.

But this still results in excess and wasteful work, because the object has to transition through two or more states.

You start with an object with parameters and defaults, and you validate them. And then you change on of the attributes immediately, validating it AGAIN. In the mean time, your object exists in a state that it will never actually be used in.

And so everywhere you possibly can, you should be setting attributes in the constructor rather than afterwards.
my $object = Class->new(
    foo => 1,
    bar => 2,
);
$object->do_something;
Less state, less complexity, less CPU, and less bugs.

If we accept this model of pushing all the configuration into the object up front to reduce state, then why change the object arbitrarily?

In fact, anything that you ARE going to change should be done under very controlled conditions.

It should require a dedicated method to apply the change, it should require validation, and work. It shouldn't be trivial, and it shouldn't be automatic.

If I had my way, Moose would set is => 'ro' by default, to make people think before they go about simply allowing stuff to change. It also happens to let you shrink down the API markedly.

There are three potential use cases available when implementing accessors. Everything readonly, everything readwrite, or mixed.

With Object::Tiny, I was aiming for the smallest possible code.

Implementing either all-readonly or all-readwrite can be done with the following.
use Class qw{
    foo
    bar
};
By contrast, if we want to allow mixed readonly and readwrite, we would need some way of distinguishing. Something like the following.
use Class {
    readonly => [ 'foo' ],
    readwrite => [ 'bar' ],
};

use Class [ qw{ foo } ], [ { bar } ];

use Class { foo => 'ro', bar => 'rw', };
No matter how you try, there's always an inherent additional element of complexity that results from the split between them.

And so the decision to go with all-readonly in Object::Tiny is a combination of these two issues.

If went with all-readwrite, I'm practically encouraging bad behaviour and more bugs. If I went with mixed accessors, the API would remain relative complex.

In the end, the best way to achieve both API simplicity and code safety is to only provide read-only accessors, and anything more complex should require both though and effort.


Immutable objects

zby on 2010-08-15T09:13:10

Thanks for the reply, what you write here is very close to nothingmuch's two essays: Immutable Data Structures and Immutable Data Structures (cont.). As a comp-sci graduate and ML programmer I like this style of programming and I understand very well these arguments, but just a few months ago, following links from Psychology of Perl talk links, I encountered this article: Usability Implications of Requiring Parameters in Objects' Constructors which states:

A comparative study was performed to assess how professional programmers use APIs with required parameters in objects' constructors as opposed to parameterless "default" constructors. It was hypothesized that required parameters would create more usable and self- documenting APIs by guiding programmers toward the correct use of objects and preventing errors. However, in the study, it was found that, contrary to expectations, programmers strongly preferred and were more effective with APIs that did not require constructor parameters.

Re:Immutable objects

hdp on 2010-08-15T13:41:25

That article's irrelevant.

1) We're talking about named constructor arguments vs. attribute setters, not positional constructor arguments vs. attribute setters; we already know that named > positional for readability, maintainability, etc. in most cases. OK, now we also know that that's even true if the named arguments are spread out over several lines of code. Big deal.

2) How people interact with constructor arguments has absolutely nothing to do with the potential complexity of your objects introduced by letting attributes be changed not only immediately after construction but also 5 packages away and 12 scopes up.