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 ]

pdcawley (485)

pdcawley
  (email not shown publicly)
http://www.bofh.org.uk/
AOL IM: pdcawley (Add Buddy, Send Message)

Journal of pdcawley (485)

Tuesday March 12, 2002
11:48 AM

More things worth stealing from Smalltalk

[ #3463 ]

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.

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.
  • 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 w
    • 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);
    • 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.

  • FWIW, a CODE tag doesn't do any formatting. It doesn't preserve formatting. It is just like using the TT tag or something.
    • Weird. 'cos where I typed a two space indent I'm seeing four spaces in the output. Very annoying.
      • 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)
        • 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 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
    • 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

  • 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;".
    • 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)
                   

      • 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 $sel

        • How do people indent code here?
          In my case I use the CODE tag, the occasional &nbsp;, and multiple drafts.
        • 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.
          • 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 deb