Ever have one of those days when you insist upon coding yourself into a programming hell? I had a problem with a very tricky data structure that was forcing me to pull out common elements from a hashref of arrays and do some weird transformations that were very easy to describe, but very difficult to implement. By the time I was done, I used tye's mapcaru (think of a map that works on multiple arrays simultaneously) function and in a space of about 30 lines, I had a total of 14 maps, greps, and mapcarus. Somehow, it worked perfectly, but I'm going to have to throw it all away because I barely understand it and I know that whoever maintains it won't understand it. The absolute worst section:
@bad_names = map { $_->[0] } @bad_names; my $array = mapcaru( sub {[@_]}, @{mapcaru( $get_common_words, @bad_names )}); my @clean_names = map { join ' ', @$_ } map { [non_empty(@$_)] } @$array; my %names = map { @$_ } mapcaru( sub {[@_]}, \@name_ids, \@clean_names);
When even I can't read it, it gets a bit embarrassing. I'm still trying to figure out why I get myself into the "I will make this square peg fit into the round hole" mindset. I'm much better than I used to be, but the above is one of the worst monstrosities I think I've written. On the other hand, the tests pass with flying colors.
True, most Perl programmers would have trouble maintaining it, and this is a valid concern.
Maybe you should comment it heavily and stare at it until you understand it forward and backwards. There's a lot to be gained by functional programming. Generally, the fewer the intermediate variables and the less the code, the _better_ the maintenance in the long run.
Obnoxious comment. Could you make it even more functionally gnarly with something like this (untested)?
my %names =
map { @$_ }
mapcaru( sub {[@_]}, \@name_ids,
[map { join ' ', @$_ }
map { [non_empty(@$_)] }
mapcaru(
sub {[@_]},
@{mapcaru(
$get_common_words,
[map { $_->[0] } @bad_names] )})]);
?
Don't hit me!
Re:Don't be so hard on yourself
Ovid on 2003-08-02T19:58:49
Aack! I'll pretend you didn't write that
:) One of the reasons why I break things into smaller steps like I did is that it's easier to insert debugging statements. As for the above, I wound up using an entirely different strategy suggested by Gav.
sub _set_up_names {
my ($self,$products) = @_;
my $length = 0;
my @names =
map {
local $_ = [$_->id, [split ' ', $_->name]];
$length = @{$_->[1]} if @{$_->[1]} > $length;
$_
}
@$products;
$length -= 2;
my $count = @names;
# assume length > 0 -- must test
my ($index,$name) = (0,'');
FIND_COMMON_NAME: {
$name = '';
foreach $index ( reverse 0.. $length ) {
$name = join ' ', @{$names[0][1]}[0.. $index];
if ($count == grep { $name eq join ' ', @{$_->[1]}[0..$index] } @names) {
@names = map { $_->[0], join ' ', splice @{$_->[1]}, $index + 1 } @names;
last FIND_COMMON_NAME;
}
}
}
my %attributes = @names;
return ( $name, \%attributes );
}It's still pretty wonky (and the above is just a first pass), but it solves my problem. It also much smaller than my original solution of which my earlier snippet was only a small subset.
Re:Don't be so hard on yourself
jordan on 2003-08-02T21:47:46
I've heard this argument about inserting debug statements, but I think it's oversold.
If I have something in an extreme functional style and I need to debug it, I just replace one of the function calls with a call to a function_debug wrapper. In my above extremely functional style, you could add map_debugs or mapcaru_debugs which would print arguments and then apply the map or mapcaru function.
One added advantage of this is that you can get all your maps or mapcarus to confess at once, often yielding unexpected insight.
However, I do think that turning Perl on it's head to look like Lisp is probably not a good idea. As I alluded to above, maintainability is a good argument against this sort of thing. It is pretty arrogant to assume that everyone will be in love with your own odd taste for functional style.
OTOH, I do spend a lot of time correcting annoying bugs with interim variables, like I might use $name for $names in your above example. Sure, this is quickly found, but it's annoying and you can stare at it for a few minutes wondering what you've done wrong.
When I write in a functional style, I sometimes feel that there are fewer things that can go wrong. But, I'm not sure.
The short functional solutions sometimes just feels so elegant, but that's a personal taste thing.
You could always make the arguments of a smug Lisp weenie:
As for me, a smug Perl weenie, I think you may go better pulling out a couple of subroutines. I don't write a lot of loops anymore.
Re:The Functional Approach
Dom2 on 2003-08-02T20:28:07
So the Smug XP Weenie says "Extract Method!" instead?;-) -Dom
Re:The Functional Approach
chromatic on 2003-08-02T21:47:19
I could have sworn I put on my "maintainable code" hat this morning. Oh well!
If your Ward Cunningham number is appropriately low, you'll know a Confident XP Weenie would say, "What would happen if you extracted a couple of those transformations into their own methods?" Unlike smug weenies, confident weenies are subtle.
Re:The Functional Approach
pdcawley on 2003-08-03T06:46:33
Guess who is kicking himself for missing the chance to get his Ward number down to 1 at OSCON this year? Ward even asked me to show him something in the Squeak image and I said "Um... bit busy, can I catch up with you later" and I never did.