Things we can learn from Smalltalk

pdcawley on 2002-03-06T13:16:37

Okay, if you're not interested in Object Orientation, move right along the bus...

I've been reading a good deal of stuff about Smalltalk recently, including a collection of Kent Beck's columns for the Smalltalk Report and there's some really good stuff in there. Kent's really big on intention revealing code. He argues that, by careful choice of names allied to sensible decomposition of 'large' methods, you end up with code that is, to all intents and purposes, self documenting.

Here's an example of this, written in Smalltalk. (The essentials, for understanding: ^ means 'return the value of the following expression'. @ is a method of Number that takes another Number and returns a Point.) extent ^(self textWidth + self leftBorder + self rightBorder + self margin) @ (self textHeight + self topBorder + self bottomBorder + self margin) Even if you understand Smalltalk syntax, this isn't exactly a model of clarity. One option is to introduce some temporary variables to explain what's going on: extent | x y | x := self textWidth + self leftBorder + self rightBorder + self margin. y := self textHeight + self topBorder + self bottomBorder + self margin. ^x @ y And there's not denying that it's much clearer. But the best way of doing it is to apply Beck's Composed Method pattern: extent ^self width @ self height

width ^self textWidth + self leftBorder + self rightBorder + self margin

height ^self textHeight + self topBorder + self bottomBorder + self margin
Now let's look at that same composed method done in Perl. sub extent { my $self = shift; [$self->width, $self->height]; }

sub width { my $self = shift; $self->text_width + $self->left_border + $self->right_border + $self->margin }

sub height { my $self = shift; $self->text_height + $self->top_border + $self->bottom_border + $self->margin }
I confess that, looking at the perl rendition of this, I start to see why people think it's ugly, that repeated my $self = shift line at the top of each and every method definition is visual cruft. In some sense it's there for Perl's benefit and not for the programmer. If you've read Tufte you may have come across the idea of data ink. In the Smalltalk example, almost every character is data ink. In the perl example, 17 chars of every method are 'wasted' with repetition.

Maybe that's why even relatively well factored Perl OO systems have fewer, larger methods than a Smalltalk system that does the same thing.

So, I'm seriously considering writing myself a source filter that will allow me to write: method extent { return [$self->width, $self->height]; } or maybe even method reduce ($initial_value, $a_coderef, @list) { $initial_value = $a_coderef->($initial_value, $_) for @list; return $initial_value } I think this is going to be relatively simple to write; I'm not after anything fancy in terms of argument list parsing or anything. The problem is, of course, that doing it right involves having to parse perl, but I'm hoping that Text::Balanced will be good enough.

Oh, for a perl version of Scheme's define-syntax; I'm keeping my fingers crossed for Perl 6...


Smalltalk

djberg96 on 2002-03-06T13:57:02

Minor nit - you don't *need* to use shift if you don't want to; e.g.

sub extent{ [$_[0]->width;$_[0]->height] }

Not as pretty perhaps, but then if you want pretty I'd recommend Ruby over Smalltalk. Another problem I have with Smalltalk is that there are too many versions and most aren't free.

Here at work I've heard that the Smalltalk developers are stuck with a 10 year old version of the language because someone, now long gone, overloaded some of the base classes a long time ago. No one knows enough about the changes to risk an upgrade because they're not sure how it will affect some of the existing software!

Re:Smalltalk

pdcawley on 2002-03-06T16:21:56

Erm. I think you may be missing the point here. $_[0] is a very, very long way from being an intention revealing variable name.

And I'm not seriously considering moving to Smalltalk, I'm just trying to learn lessons from the experience of top class Smalltalkers. (Something which is obviously going on in the Ruby world)

Re:Smalltalk

james on 2002-03-06T18:46:42

Its a nasty hack, and it does use the deparser (once again). Under strict your original code will barf. No real way around that. I know it could be done with a source filter, but I don't like source filters, so its done with deparse instead.

package Attribute::Method;
use strict;
use warnings::register;
use Attribute::Handlers;

sub UNIVERSAL::method : ATTR(CODE) {
    my ($package, $symbol, $referent, $attr, $data) = @_;
    my $b = B::Deparse->new();
    my $code = $b->coderef2text( $referent );
    $code =~ s/\:(.+?){/{/; # strip attributes
    *$symbol = sub {
        my $self = shift;
        my $sub = eval "sub $code";
        if (!$@) { return $sub->( @_ );
        warnings::warn($@) if $@;
    }
}

1;

For example, with the above, then you could use:

sub name : method {
    return $self->{name};
}

and it all magically works (with a great many caveats :-).

Re:Smalltalk

Matts on 2002-03-07T07:25:21

/me points to CPAN!

Please upload that. It's awesome!

Re:Smalltalk

pdcawley on 2002-03-07T08:50:28

Nice hack. But I'm afraid the caveats are too many and various for me... Closure issues, deparse inaccuracies, not necessarily working desperately well for classes that get loaded at runtime, that sort of thing.

Source filters definitely slow startup, but you can amortize that with long lived processes and if you wanted speed, why are you programming in perl and, more especially, why are you doing it with perl OO?

Repeatedly recompiling the code looks scary... doesn't this work just as well:

sub UNIVERSAL::method : ATTR(CODE) {
        my ($package, $symbol, $referent, $attr, $data) = @_;
        my $b = B::Deparse->new();
        my $code = $b->coderef2text( $referent );
        $code =~ s/\:(.+?){/{/; # strip attributes
        $code =~ s/\{/\{my \$self = shift;/; # get $self
        my $subref = eval "package $package; sub $code";
        warnings::warn($@), return if $@;
        *$symbol = $subref;
}

It still has the problem with lexical variables that were visible to the original code not being in scope any more though. (So don't do that then...) And you need the package declaration in there or SUPER will stop working (but that's a rant from another day)

Of course, if you want to make it really Smalltalkish, you can arrange it so that any method that doesn't explicitly return something returns $self with this regex:

    s/;?\s*\}([^}]*)$/;\nreturn \$self}$1/

Re:Smalltalk

james on 2002-03-07T09:42:51

yup thats better.

Like I said, it was a quick hack in reaction to your post about wanting to be able to do it.

It can be done.

You have to know the caveats.

It saves ink.

You shouldn't be doing anything too clever in methods anyway, if they are small enough (I've been reading the same book Piers :-)

Simple methods should deparse ok (esp. with modern perls [read: blead]). However, if you are performing some sort of magic then its always going to be complicated, no matter what language you're writing code in, and those few characters of 'my $self = shift;' at the start shouldn't worry you too much :-)

Re:Smalltalk

pdcawley on 2002-03-07T10:50:54

It's a jolly good book isn't it? Kent Beck is a very wise man (and SmallTalk Best Practice Patterns is far and away the best patterns book (that isn't be Alexander et al) I've ever read; the patterns actually hang together as a pattern language)).

I take your point though. The real killer for me is that, last time I played with it, Attributes didn't work that well with runtime loading of classes (which can be remarkably useful...)