mandog has asked for the wisdom of the Perl Monks concerning the following question:
In node this node ovid very kindly points out a couple of my silly mistakes. He corrects my code and offers another clearer sub. Ingrate that I am, I'm not sure the new sub is acutally clearer....:
sub Ovid_Clear_MakePtag{
my $fixme = shift; # take in our parameters
return join "\r\n", # join with newlines
map { "<p>$_</p>" } # wrap each line in <p></p> tags
grep { /\S/ } # must have at least one non-whitespace
+ character
split "\r\n", $fixme; # break apart on the newlines
}
sub MakePtag{
chomp(my $fixme = shift;) # take in our parameters
$fixme=~s|(\r\n)|</p><p>|g; # replace all \r\n with <\p><p>
$fixme = "<p>$fixme</p>"; # Add beginning and ending tags
return $fixme;
}
In the new version there are 6 concepts to understand (shift,join, map,split, comments within an expression, return) 5 lines and one relatively long expression.
In the old version, there are 5 concepts (shift,chomp,s///, interpolation, return) and four lines with four pretty simple expressions.
Part of the issue is that I'm more familar w/ the original 5 concepts than ovid's new ones. However, I'm guessing that most people find it easier to read several short expressions instead of one long one...
Am I missing something here?
email: mandog
Re: What is clear code ?
by tadman (Prior) on Apr 29, 2002 at 02:17 UTC
|
It's not necessarily the best teaching example due to the complexity of it all, but it does show how Perl can work. The "chained" function system is often very useful in data-translation exercises, such as this.
It might be a little tidier as such:
sub Clear_MakePtag
{
return join ("\r\n",
map { "<p>$_</p>" }
grep { /\S/ }
split ("\r\n", my ($fix) = @_)
);
}
sub MakePtag
{
my ($fix) = @_;
$fix =~ s!\r\n$!!s;
$fix =~ s!\r\n!</p>\r\n<p>!g;
return "<p>$fixme</p>";
}
Cosmetic mostly, but since this is about clarity. The chomp call seems kind of strange since it takes off $/, which is OS specific, right? Better to just say what you mean, I would think. | [reply] [d/l] |
|
update: In the original node, I'm probably not as clear I should be that much of the second sub is ovid's
Yes, while the chomp better than the no chomp, it is not perfect. If the string is something like "asadf\r\n" and the OS is \r\n orriented, the chomp gives us "<p>asdf</p>" instead of "<p>asdf</p><p></p>". If we are processing the string on a \n orriented OS, We are out of luck...
If I understand perldoc perlre the "/s" or "!s" at the end of the regular expression means "Treat string as single line. "
However, in tadman's node, I'm not getting how s/\r\n//s ( or s!\r\n!!s ) means "take off the \r\n at the end of the string" ???
email: mandog
| [reply] [d/l] [select] |
|
$fix =~ s!\r\n$!!s;
Notice the '$' mark in the line: it anchors the '\r\n' to the end of string. Meaning, replace \r\n only if it appears as the last thing in the string.
| [reply] [d/l] |
Re: What is clear code ?
by chromatic (Archbishop) on Apr 29, 2002 at 05:20 UTC
|
Maybe it helps to consider code clarity algebraically. While it's possible to write 2x = x^2, if you want to solve the equation, you need to isolate your variables on one side or the other. Maybe I'm stretching to make a connection to idioms, but I find the second of these pairs much clearer:
$i = $i + 1;
$i++;
$i = $i + $i;
$i *= 2;
Unfamiliarity with Perl's list-processing features does tend to obscure the intent of Ovid's code somewhat, but I can follow it without the comments. (I'd argue it would be clearer with a single comment that said "strip empty lines, wrap paragraphs marked by newlines in paragraph tags".)
I'm not completely satisfied with either -- it's not clear why "\r\n" is used instead of $/, though Ovid's has a distinct advantage of the <p>$_</p> construct. | [reply] [d/l] [select] |
Re: What is clear code ?
by abstracts (Hermit) on Apr 29, 2002 at 02:22 UTC
|
The 2 programs listed are not equivalent. If you want to rephrase Ovid's example to match your example, it would be something like:
sub Ovid_Clear_MakePtag2{
my $fixme = shift; # take in our parameters
return join "", # join with nothing
map { "<p>$_</p>" } # wrap each line in <p></p> tags
split "\r\n", $fixme; # break apart on the newlines
}
Or even:
sub Abstract_Clear_MakePtag{
my $fixme = shift; # take in our parameters
return "<p>" .
join("</p><p>", split "\r\n", $fixme) .
"</p>"
}
which uses 2 concepts: split and join (really one concept).
Would this make a good "clear" example?
As for my opinion, I'd go with your example being the clearer of the 3 examples so far.
| [reply] [d/l] [select] |
Re: What is clear code ?
by clintp (Curate) on Apr 29, 2002 at 02:25 UTC
|
Don't feel bad. Strictly on readability, I actually like yours better. Not necessarily because of the "number of concepts."
For me it's simply because it can be read top-down (instead of bottom-up). I despise having to visually scan down several lines for the semicolon, and scan back up to see what's being done. With all of the comments, yours is far more readable to me. Remove the comments, and his becomes much better. One grouping, one explanation.
Ovid's would be re-written as: sub Readable_Ovid_Clear_MakePtag{
my $fixme = shift;
# make newline-breaks into HTML paragraphs
return join "\r\n", map { "<p>$_</p>" }
grep { /\S/ } split "\r\n", $fixme;
}
| [reply] [d/l] |
|
| [reply] |
Re: What is clear code ?
by jlongino (Parson) on Apr 29, 2002 at 03:19 UTC
|
Although your code might be a bit clearer to read, as a few other posts say, the functionality is not the same. Given this code:
my $str = "Line 1 has text, but next is an empty line:\r\n\r\nThird li
+ne has text\r\n";
print "Original String:\n'$str'\n\n";
print "Ovid:\n", Ovid_Clear_MakePtag($str), "\n\n";
print "mandog:\n", MakePtag($str), "\n";
sub Ovid_Clear_MakePtag{
my $fixme = shift; # take in our parameters
return join "\r\n", # join with newlines
map { "<p>$_</p>" } # wrap each line in <p></p> tags
grep { /\S/ } # must have at least one non-whitespace
+ character
split "\r\n", $fixme; # break apart on the newlines
}
sub MakePtag{
chomp(my $fixme = shift); # take in our parameters
$fixme=~s|(\r\n)|</p><p>|g; # replace all \r\n with <\p><p>
$fixme = "<p>$fixme</p>"; # Add beginning and ending tags
return $fixme;
}
The following results are produced:
Original String:
'Line 1 has text, but next is an empty line:
Third line has text
'
Ovid:
<p>Line 1 has text, but next is an empty line:</p>
<p>Third line has text</p>
mandog:
</p>ine 1 has text, but next is an empty line:</p><p></p><p>Third line
+ has text
--Jim | [reply] [d/l] [select] |
Re: What is clear code ?
by erikharrison (Deacon) on Apr 29, 2002 at 03:57 UTC
|
There is something to keep in mind when talking about "clean" code. Code that operates within a languages strengths in usually more robust, but also more "natural". Sure, you use a regular expression, but Perl isn't really about regular expressions (entirely). Perl is more generally about list processing.
Consider the Schwartzian Transform. This is a powerful took which is the direct product of list oriented thinking. Ovid's solution stems from Perlish thinking. map and grep are list tools, and Ovid combines them into something that is clearly in the languages idiom, and therefore "clean". An anology would be the sentence "Sarcasm is the lowest form of wit I think not".It is grammatically correct, and uses few grammatical concepts. But it is hard to read and understand because it is outside of the languages idiom.
Cheers,
Erik
| [reply] |
Re: What is clear code ?
by sfink (Deacon) on Apr 29, 2002 at 22:40 UTC
|
In my mind, there are two main differences between those chunks of code (ignoring the differences in functionality). First, the algorithm is different. Second, the top one chains expressions. I'll rewrite the top one without the chaining: sub YetAnotherMakePtag {
my $fixme = shift;
my @lines = split "\r\n", $fixme;
my @nonblank_lines = grep { /\S/ } @lines;
my @wrapped_lines = map { "<p>$_</p>" } @nonblank_lines;
return join("\r\n", @wrapped_lines;
}
I would argue that this version is clearer than either original, with or without comments. (Probably the slowest, too.) While the chaining made things more confusing, the algorithm change actually made things clearer. And that's because the concepts in the new version, however many there may be, are a direct mapping to a natural conception of how to perform the task. In MakePtag(), there is an intuitive leap required to see that you can wrap delimiters around a string by replacing separators with close-delimiter open-delimiter pairs, and then wrapping the whole thing with open-close.
I try to avoid that construct in my code. For example, I always do
print "$_\n" foreach (@list)
instead of
print join("\n", @list), "\n"
simply because the first is a more natural translation the actions I wish to perform. (It also reduces repetition of the terminator, and is much more open to modification if you want to do something a little different: print "-->$_<--\n" foreach...).
On the other hand, for pedalogical purposes I think Ovid's is best. It answers your question, it gives a more complete solution, and it introduces a useful technique (chaining) that doesn't particularly obscure the clarity of the example because it's so easy to see the transformation I did above. | [reply] [d/l] [select] |
|
|