More things worth stealing from Smalltalk

pdcawley on 2002-03-12T16:48:07

I've spent the day working on what has since become called Pixie, the object oriented database framework based around James's clever trick. Both I and the Fotango people have been working on 80/20 type solutions, but we appear to have different ideas about what constitutes the important 80%. So, I think I'll be merging tonight.

While I was beavering away on this, I finally got one of the classic OO commandments. This one's about constructors:

Your basic constructor, new should not take any arguments and should simply return an empty object
This seems somewhat counter intuitive; you're always going to set the attributes to something or other, or you're going to something else at instantiation time. Well, yes. And that's what factory methods are for. A new that takes an argument is an accident waiting to happen once you start doing the funky inheritance thing; you're going to have to remember what kind of mangling will go on elsewhere in the class hierarchy and your head is going to ache. 'Simple' constructors can also help with things like Class::MethodMaker's very lovely 'object' directive.

If you've got object invariants to worry about, don't. You can get 'round that by only calling the constructor via factory methods in the class (invariants should only be checked when flow of control leaves the object's methods after all).

If you combine this with Smalltalk type methods (which return $self unless there's a very good reason to return something else; even setter methods return $self) then you get to write nice code like the following: package Object; use Data::UUID; use Time::Piece;

sub new { my $class = shift; my $self = bless {}, $class; $self->initialize; return $self; }

sub initialize { my $self = shift; $self->init_oid  ->init_timestamp; }

sub init_oid { my $self = shift; $self->{_oid} = Data::UUID->new->create_str; $self; }

sub init_timestamp { my $self = shift; $self->{timestamp} = Time::Piece->new; $self; }

package Derived;

use base 'Object';

sub initialize { my $self = shift;

$self->SUPER::initialize  ->init_foo; }

sub init_foo { ... }
By returning $self even for setting methods (how often do you really use the return value of a setting method?) you can move to code like: sub factory_method { my $proto = shift; my ($foo, $bar) = @_;

$proto->new->set_foo($foo)  ->set_bar($bar); }
which, to these old eyes at least looks neater (modulo the <code> tag screwing up my formatting) and takes less time to type. In perl 6 it'll probably look even neater.


Returning $self

Matts on 2002-03-12T17:31:19

Returning $self is something I'm very mixed on, but I think that's mostly because I do coding in such mixed environments - spending some of my time in C, and some in Perl. In C, the classic idiom of a setter is to return the old value. I'm not entirely sure that's worth it, as the use-cases seem to be few and far between. So I'm starting to use the return $self idiom far more often.

One module to note on this is SOAP::Lite, which does almost nothing BUT return $self on each call. And it seems to do pretty well for it!

Re:Returning $self

autarch on 2002-03-12T17:58:33

One of the modules in Alzabo does (the SQL constructor) so I can write code like this:

$sql->select(@things)->from(@tables)->where(@conditions)->order_by(@ other_things)->limit(10);

Re:Returning $self

Matts on 2002-03-12T19:31:54

Sheesh! All that to avoid writing SQL??? Man, some modules need to get a life ;-)

Re:Returning $self

autarch on 2002-03-12T22:08:49

All that to be able to write SQL in a cross-platform manner.

Re:Returning $self

djberg96 on 2002-03-14T18:42:14

Just in case you're only following replies to your own messages, see my hint above regarding Robin Houston's Want module. You can return $self or another value based on context that way.

I should have read down further myself, as I just saw the remarks about ECODE.

CODE tag

pudge on 2002-03-12T20:32:30

FWIW, a CODE tag doesn't do any formatting. It doesn't preserve formatting. It is just like using the TT tag or something.

Re:CODE tag

pdcawley on 2002-03-12T22:33:19

Weird. 'cos where I typed a two space indent I'm seeing four spaces in the output. Very annoying.

Re:CODE tag

pdcawley on 2002-03-12T22:36:27

And, on closer inspection of the page source, spaces in the input have been replaced by '&nbsp; ', that extra space appears to be what's screwing things up. (Unless it's 'Plain old text' and <code> failing to get on)

Re:CODE tag

pudge on 2002-03-12T22:42:05

I dunno. I will soon be adding ECODE (it is there now, but I need a few more fixes for it) which will allow you to seamlessly integrate blocks of code with regular text in plain text or HTML modes. Maybe tomorrow morning (I have some more testing to do with that and one other thing).

Factory methods and deffered binding

dws on 2002-03-12T21:00:29

Factory methods do indeed return newly created and initialized instances (something you've gotten wrong in your example), but they also have the effect of deferred binding. The calling method is no longer explicitly naming a class. Rather, this detailed is hidden in the factory method, which is free to have either bind a class (package) name at compile time, or choose one at runtime. You'll see some of this in Smalltalk (at least in Objectworks and its successors) using a predecessor to the Strategy pattern. When invoking a factory method to create a UI widget, a "Policy" object is consulted to determine what "type" of widget to return.

Re:Factory methods and defered binding

pdcawley on 2002-03-12T22:49:09

Bugger. I could have sworn I typed

$class->new->set_foo($foo)
          ->set_bar($bar);

[fx: Hits 'edit' and fixes the code]

And I am aware of the late binding stuff. Perl makes that sort of thing so easy it's almost second nature now. In the Pixie::Proxy stuff I've been working on I have subclasses Pixie::Proxy::HASH, Pixie::Proxy::ARRAY etc. Pixie::Proxy::make_proxy($target, $store) looks at the target object to find out which subclass to instantiate, then does

    $class->new
          ->set_target($proxy)
          ->store($store);

and returns a proxy object in the appropriate class. It's lovely.

I've never really understood why people don't seem to get factory methods; surely they're obvious. I remember reading Design Patterns and looking at the factory method pattern and wondering why they'd bothered to write it down.

smaltakeetakee

TorgoX on 2002-03-13T07:27:54

The problem that I see with returning $self when you don't need to return anything else is basically that the user's expectations of when you need to do it, might not match yours. Does $wooz->save() return $foo, or the success of saving it? Well, you run perldoc WoozleWuzzle, and look up its "save" method, but by time you've done that, you could've just written "$wooz->save; $wooz->print;" instead of bothering to find out whether you could score style points by writing "$wooz->save->print;".

One also never knows whether accessors like $wooz->name("foo") "should" return nothing, return $wooz, return "foo", or return the old value of $wooz->name(). If it's an arbitrary choice, I usually figure that relying on it just for style, is usually a bad idea. But that's just me.

As to options in constructors, I agree. Just about the only constructor that I appreciate taking an option is HTTP::TokeParser's ->new(\$document) or ->new("filename"). But that's 'cause TokeParser is a model of whipupitude, not internal neatness or subclassability. If I were to do it over, I'd make a parameterless ->new(), and then make a shortcut ->new_from_source($source) (and call it such in the docs) which is just this
sub new_from_source {
 my($class, $source) = @_;
 my $x = $class->new;
 $x->source($source);
 return $x
}

Re:smaltakeetakee

pdcawley on 2002-03-13T09:22:21

Does $wooz->save() return $foo or the success of saving it
Applying the 'die early' pattern I'd have to say that I'd implement it with something like:

    sub save {
        my $self = shift;
        $self->_do_real_save or
            die IOException->new
                           ->obj($self)
                           ->msg("Couldn't save $self");
        return $self;
    }

And I if I found myself using that exception idiom a lot, I'd be tempted to write a method somewhere up at the top of my class tree (UNIVERSAL perhaps) like:

    sub throw {
        my $self = shift;
        my $exception = shift;
        die $exception->object($self);
    }

And refactor Exception's methods a little so that a typical one would look like:

    sub set_attr {
        my $proto = shift;
        $proto = ref($proto) ? $proto : $proto->new;
        my $attr = shift;
        my $value = shift;

        $proto->{$attr} = {$value};
        return $proto;
    }
   
    sub msg {
        my $self = shift;
       
        @_ ? $self->set_attr(msg => shift) : $self->{msg};
    }

Hmm... that's a bit ugly. I'm actually more likely to seperate getters and setters.

Anyhow, the original example becomes

    sub save {
        my $self = shift;
        $self->_do_real_save or
            $self->throw(IOException->msg("Couldn't save $self"));
    }

Mmm... lovely.

As for the 'should setting methods return the new/old value or the object question, remember that generally the only thing that should be manipulating an objects attributes is the object itself. Outside objects monkeying with an object's state directly is often a 'Code smell'. So write the accessors/setters to do what is useful to you. Document that fact and let the rest of the world go hang. If you find you're doing $self->attrib($value)->attrib more often than doing $self->attrib1($value)->attrib2($value2) then don't return the object, return the value, or write your attribs as lvalue subs, or whatever...

Re:smaltakeetakee

djberg96 on 2002-03-14T14:13:49

Does $wooz->save() return $foo or the success of saving it

Here is where Robin Houston's Want.pm module gives you the power you need to decide - CONTEXT.

$wooz->save;
$wooz->save->print;
$val = $wooz->save;
someMethod($wooz->save);
@val = $wooz->save;

These can all be differentiated with Want. So, using TorgoX's example, I'd code it like this:

use Want;

sub save{
my $self = shift;
$self->_do_real_save or die "Blah...";
if(want('OBJECT')){ return $self }
return 1;
}

Sorry about the lack of indentation. The CODE tag doesn't seem to work as expected. How do people indent code here?

Re:smaltakeetakee

pdcawley on 2002-03-14T15:05:26

How do people indent code here?
In my case I use the CODE tag, the occasional &nbsp;, and multiple drafts.

Re:smaltakeetakee

pudge on 2002-03-15T17:26:32

The new ECODE tag is now supported!

Re:smaltakeetakee

pdcawley on 2002-03-14T15:14:56

want('OBJECT') works? Oh wonderful. That means it might be possible to do Smalltalk type method selectors:

    ...
    $dictionary->at($index)->insert($value);
    ...
    $foo = $dictionary->at($index);
    ...

Class::Smalltalk anyone?

No, I will not be working on such a class myself in the immediate future, but it would be a neat hack.

Re:smaltakeetakee

djberg96 on 2002-03-14T16:29:33

want('OBJECT') works? Oh wonderful.

Yep! I discovered Want somewhat by accident, when people told me that the kind of contextual information I wanted wasn't possible. That's when I decided to scour CPAN. My search paid off.

It was his module (and Ruby) that inspired my Set::Array and Set::String modules.

The only problem right now with Want is that it doesn't get along with overload. You can do boolean tests with overloaded ops, but that's about it. Anything else will cause a crash. Using the debugger with Want will almost certainly cause it to crash as well.

Robin is aware of these issues. I eagerly await .06!

Re:smaltakeetakee

robin on 2002-03-14T20:17:05

Thanks for reminding me!