I was thinking about putting together a talk about different programming styles and I decided to loosely translate the following snippet from Squeak (a free implementation of Smalltalk):
hello: times say: text (times > 100) ifTrue: [ Transcript show: 'You will get bored!'] ifFalse: [1 to: times do: [:i | (Transcript show: text) cr]]
It's like some kind of bizarro mirror universe where everything looks OK until you peer closely at it. Since Squeak doesn't have if/then statements, what's really going on is the "greater than" comparison is actually a constructor and ifTrue and ifFalse are actually messages sent to the boolean object. It can choose to act on those messages or not and if it does, the code in the square brackets is evaluated (think "anonymous sub".) As a result, you really do have complete encapsulation and you could even reorder those messages if you so desired. In a traditional if/else statement, that's not always as easy.
To translate that into Perl, you might have the body of the method looking similar to this:
(Boolean->gt($times, 100)) ->ifTrue( sub {print "You will get bored!" } ) ->ifFalse( sub {print $text for 1 .. $times } );
(Yes, I cheated with the range operator.)
And the relevent methods in Boolean might look like this:
package Boolean; sub gt { my ($class, $num1, $num2) = @_; bless { true => ($num1 > $num2) } => $class; } sub ifTrue { my $self = shift; $self->{true} && shift->(); return $self; } sub ifFalse { my $self = shift; ! $self->{true} && shift->(); return $self; } 1;
No if statements at all (though they're there in disguise.)
I'm not recommending this particular technique, but I did think it was a fascinating look at "pure OO" and its implications. This has proven very helpful to me as it taught me to take a closer look at how I use if statements. Much of my code is now conceptually cleaner due to finding a different way of thinking about the problem.
Re:Pragmatism versus Purity
merlyn on 2005-01-20T18:02:31
Smalltalk syntax is like lisp syntax. It's deliberately small so that it can be parsed easily, and reflexive programming is easy.Squeak also includes a more traditional syntax if you choose to use it (most don't), but can still map the one syntax to the other, without even losing comment locations.
Or, you can program in a tile-like environment in Morphic, which again builds Smalltalk methods from the tiles.
The nice thing about Smalltalk is that it's a way of life, not a language. And every part of it is tweakable.
#!/usr/bin/perl
use strict;
use warnings;
use Test::More tests => 2;
{
package Boolean;
use strict;
use warnings;
sub import {
no strict 'refs';
*{(caller())[0] . '::cond'} = \&cond;
}
sub cond (&) {
my ($block) = @_;
return bless $block, "Boolean";
}
sub isFalse {
my ($self, $sub) = @_;
$sub->() if not $self->();
return $self;
}
sub isTrue {
my ($self, $sub) = @_;
$sub->() if $self->();
return $self;
}
sub whileTrue {
my ($self, $sub) = @_;
$sub->() while $self->();
return $self;
}
}
BEGIN {
# this would normally be 'use Boolean'
Boolean->import();
}
{
my $e = 0;
cond { $e < 10 }->whileTrue( sub { $e++ } );
cmp_ok($e, '==', 10, '... $e is 10 now');
}
{
my $e = 2;
cond { $e == 2 }->
isTrue(
cond { $e == 3 }->
isFalse(sub {
pass('... we got to the correct branch');
})->
isTrue(sub {
fail('we should not end up here');
})
)->
isFalse(sub {
fail('we should not end up here');
});
}
Re:This reminds me of something I did once
Ovid on 2005-01-20T20:52:47
Sweet! I would change two methods:
sub isFalse {
my ($self, $sub) = @_;
! $self->() && $sub->();
return $self;
}
sub isTrue {
my ($self, $sub) = @_;
$self->() && $sub->();
return $self;
}Maybe I can slip this into production
... (no, I'm not serious.)