After writing about variable roles a while back, I’ve been thinking a lot about the way I use variables. The post also got quite a lot of attention, so it seems to have hit a note with people. And no wonder it does: the concept kinda promises to tell you something profound about programming. The problem is that it doesn’t really deliver on the promise.

The first observation anyone with some experience in programming will see, is that the majority of the roles are loop control variables (check the post, but basically they mention “stepper”, “most-recent-holder”, “most-wanted-holder”, “gatherer” and “follower” as distinct roles, as well as “fixed-value” and “temporary”, who also could be said to be typical loop control roles.

My first idea was that if all these are necessary to traverse loops, perhaps a historized scalar variable would be helpful. I.e. the “follower” could just be a ->prev() method, there could be a sum of the history and so on. And it turns out there is a variable history module: Tie::History (just remember to turn on AutoCommit).

Now maybe that wasn’t such a great idea. Or maybe it just isn’t thought properly through. Because what happens then is that you just start doing list operations on the history. Hence, maybe the list operations are just the way to go, and a more functional programming style will get rid off the elaborate loop control variables. Or so I suggested in the post.

Here, I’m trying out some attempts at bypassing the loop control variable roles by using a more declarative/functional programming style. I’ve made some examples of typical imperative style loops, and have tried to see I could get rid of the traditional variable roles by doing it in a declarative style. Have a look and see if it seems meaningful.

(Also, it gives me an opportunity to try out the awesome CodeColorer code viewer).

my @countries = qw( USA China Netherlands
                    Norway Finland Sweden );

my $longest;  # $most wanted holder;
for my $country (@countries) { # most recent holder
    if (length $longest  < length $country ) {
        $longest = $country;
    }
}
print "Longest name: $longest\n";

In my alternative take, I realized I ended up with both sort of a “most-wanted-holder” and TWO temporary variables. However, that I want to store my value somewhere, I can’t help, and at least there are no iterations using it. The temporary variables are worse, however. The point was to get rid of them after all.

my @countries = qw( USA China Netherlands
                    Norway Finland Sweden );

my ($longest) = sort { length $b <=> length $a } @countries;

print "Longest name: $longest\n";

This version looks a bit sleeker, but sorting could be slower than a single iteration. Another approach that should have the same profile as the intial loop is reduce from the core module List::Util:

use List::Util qw(reduce);

my @countries = qw( USA China Netherlands
                    Norway Finland Sweden );

my ($longest) = reduce { (length $b > length $a) ? $b : $a }
                @countries;

print "Longest name: $longest\n";

And now we have even more internal variables in use..

Ok, the “stepper” role should be easier to remove. Here is a simple loop with a “stepper” and a “gatherer”:

my @countries = qw( China Netherlands Norway
                    USA Finland Sweden );

my $i = 0;      # stepper
my @uppercased; # gatherer
while ($i < @countries) {
    $i++;
    if (uc $countries[$i] eq $countries[$i]) {
        push @uppercased, $countries[$i];
    }
}

print "Uppercase names: @uppercased\n";

It breaks my heart to write code like this. But it is also typical imperative code! This one lends itself easier to list operations too:

my @countries = qw( China Netherlands Norway
                    USA Finland Sweden );

my @uppercased = grep { uc $_ eq $_ } @countries;

print "Uppercase names: @uppercased\n";

One construct I vividly recognize is the “follower”, described as “A data item that gets its new value always from the old value of some other data item”. I am probably not the only one having written code balancing a pair of previous/this type variables. Here is a slightly contrived example that reports every adjacent pair of the same length:

my @countries = qw( USA China Netherlands
                    Norway Sweden France Finland );

my $previous_country;  # Follower
my @pairs;      # Gatherer
for my $country (@countries) { # most recent holder
    if (length $country == length $previous_country) {
        push @pairs, $previous_country, $country;
    }
    $previous_country = $country;
}

print "Got pairs @pairs";

This did not turn out as easy as I expected. If you try to do functional operations in Perl using core libraries, reduce is basically the only method that let you operate on list items in a non-independent fashion. And the only way I found to do this with reduce is even more contrived than my imperative example:

use List::Util qw( reduce );

my @countries = qw( USA China Netherlands
                    Norway Sweden France Finland );

my @pairs;
reduce { length $a == length $b and push @pairs, $a, $b;
         $b;
       } @countries;

print "Got pairs @pairs";

Now, except from the last example, I find the declarative approach to be generally far more easy to read. While that could just be because they are shorter, I don’t think that is sufficient as an explanation for that – there is plenty of compact code that would be easier to understand if it was more explicitly stated.

Now, cognitive psychology actually has an explanation of why declarative list operations like this are easier to understand. And I’ll present the evidence for that in my next post.

Until then, there is an article about functional programming on Joel on Software that is worth reading.

Leave a Reply

Your email address will not be published. Required fields are marked *