Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

Alias (5735)

Alias
  (email not shown publicly)
http://ali.as/

Journal of Alias (5735)

Saturday August 14, 2010
08:39 PM

Why does Object::Tiny only support getters

[ #40496 ]

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.

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • Thanks for the reply, what you write here is very close to nothingmuch's two essays: Immutable Data Structures [woobling.org] and Immutable Data Structures (cont.) [woobling.org]. 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 [einarsen.no], I encountered this article: Usability Implications of Requiring Parameters in Objects' Constructors [cmu.edu] which states:

    A comparative study was performed to assess how pro

    • 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