http://qs321.pair.com?node_id=723825

mandog has asked for the wisdom of the Perl Monks concerning the following question:

Perl::Critic and Perl Best Practices book flag this loop as "C-style":

for ( my $i = 0; $i < @values; $i++ ) { $values[$i] = _clean_cgi_param( $values[$i] ); }
This is better:
my @cleaned_values; for $value ( @values) { push @cleaned_values, _clean_cgi_param($value); }

Right ?

Replies are listed 'Best First'.
Re: C-style for loop
by GrandFather (Saint) on Nov 15, 2008 at 20:21 UTC

    The fact that the code in the bodies of the loops are quite different means that "better" can't meaningfully be applied. There are situations where the C style loop is "better", but in the general case the Perl style for loop is "better".

    The C style for loop for (initialisation; condition; expression) {...} is equivalent to:

    initialisation; while (condition) { ... } continue { expression }

    where the three parts (initialisation, condition and expression) need not be related to each other in any way except they are all part of the same for loop header. That allows the C for loop to be abused in many ways. The clutter in the loop header often makes it difficult to see the various parts and the nature of the condition means that C style for loops are very prone to off by one errors.

    The Perl for loop on the other hand is simple. All it does is iterate over a list of elements. The list may be the contents of an array, or the members of a list generated using the range operator or something tricky using map, grep or whatever, but always the Perl for loop simply iterates over a list. (Internally the for loop code may employ various tricks to improve memory usage or speed, but those are unimportant to our current discussion.)

    It is worth remembering that the Perl for loop can be used as a statement modifier so your loop could be:

    push @cleaned_values, _clean_cgi_param ($_) for @values;

    Perl reduces RSI - it saves typing
Re: C-style for loop
by toolic (Bishop) on Nov 15, 2008 at 19:12 UTC
    Those 2 loops do quite different things.

    If your goal is to create a new array, @cleaned_values, from another array, @values, you could use a "for" loop like this:

    for (@values) { push @cleaned_values, _clean_cgi_param($_); }

    Alternately, you could use map for this:

    @cleaned_values = map { _clean_cgi_param($_) } @values;

    Update: The code in the OP was modified after I composed this node.

      Yes! Use map()!
Re: C-style for loop
by zentara (Archbishop) on Nov 15, 2008 at 19:18 UTC
    The C-style loop can be useful in cases where you want to skip over some values, but for other reasons(possibly ease of remembering range) want to keep your range 0..100.
    #!/usr/bin/perl for (my $i = 0; $i <= 100; $i += 5) { print($i, "\n"); } print "\n"; for (0..20){ print 5 * $_,"\n"; }
    Someone good at setting up efficiency tests, may find one better than the other in terms of what is fastest.

    I'm not really a human, but I play one on earth Remember How Lucky You Are
      Looks like Perl is faster in this case.
      #!/usr/bin/perl use warnings; use strict; use Benchmark; #Outputs: #Benchmark: timing 1000000 iterations of c-loop, perl-loop... # c-loop: 5 wallclock secs ( 5.64 usr + 0.02 sys = 5.66 CPU) @ 176678.45/s (n=1000000) # perl-loop: 3 wallclock secs ( 3.58 usr + 0.01 sys = 3.59 CPU) @ 278551.53/s (n=1000000) timethese(1000000, { 'c-loop' => sub { for (my $i = 0; $i <= 100; $i += 5) { #print("$i "); } }, 'perl-loop' => sub { for (0..20){ # print 5 * $_." "; } }, });

      I'm not really a human, but I play one on earth Remember How Lucky You Are
Re: C-style for loop
by ccn (Vicar) on Nov 15, 2008 at 19:10 UTC
    Perl style
    $_ = _clean_cgi_param($_) foreach @values;

    "C-style" is bad because of useless $i variable

      @cnn: your solution is almost the shortest (well,you can abbreviate foreach with for ; ). And I prefere this one!

      But the OP wants to conform to the styleguides of PBP & Perl Critics, and postfix-loops are disadviced!

      If I was in this situation, I would prefere

      _clean_param($_) for @values;
      with the routine transforming $_[0] directly, (it's an alias)

      UPDATE: or

      _clean_params(@values); # sub takes arr_ref by prototype

      Cheers LanX

      - - - - - Which song???

        But the OP wants to conform to the styleguides of PBP & Perl Critics, and postfix-loops are disadviced

        PBP advises against a lot of useful things. That doesn't mean that you shouldn't use them. It means that you shouldn't use them without a little bit of thought. In the right place, a C-style for loop is Just Fine. In the right place, a "postfix-loop" is Just Fine.

      No. You could write a C for loop as:

      for ($_ = 0; $_ < @values; ++$_) { ... }

      but that would be a particularly bad way to do it. A C for loop is generally a poorer solution that a Perl for loop because there are more parts to go wrong. A C style for loop is highly suceptible to off by one errors.


      Perl reduces RSI - it saves typing

        Yes, one can use $_ instead of $i, but I talked about the loop specified by OP. And if in theory we can't say what type of a loop is better but in specific case we can.

        Unfortunately OP gave not equivalent loops. The first one modifies original array and the second one creates new array with modified data. I rewrite in perl-style the first loop.

      I personally believe I may come out as an annoying little voice, but I have to join the chorus: that is a statement-modifier-for which happens to have the form of Perl-style C<for> acting on $_ - and saving you the need of parens. But by a (generic) Perl-style C<for> it is meant either:

      for my $i (LIST) { ... } # or for (LIST) { ... }

      "C-style" is in no way bad "because of useless $i variable" since indeed there may not be any $i variable at all: it's "just" a completely different kind of loop altoghether (a more generic one - and that is fundamentally the reason why it exists at all,) which bears the same name as Perl-style one due to the fact that it can easily be disambiguated from the latter by means of the syntax of what's in the parens. I would go as far as claiming that what's in the parens itself in that case is yet another minilanguage (very closely related to Perl itself: it consists of exactly three Perl statements!) that does not fit in other parts of Perl's syntax, and thus a very special case: of course Perl 6 knows better and gives it an entirely different name which IIRC is C<loop> and not by chance takes one letter more wrt "for" since that's for huffmanization and in fact it is thought to be on a much sparser basis to begin with.

      --
      If you can't understand the incipit, then please check the IPB Campaign.
        I would go as far as claiming that what's in the parens itself in that case is yet another minilanguage (very closely related to Perl itself: it consists of exactly three Perl statements!) that does not fit in other parts of Perl's syntax, and thus a very special case: of course Perl 6 knows better and gives it an entirely different name which IIRC is C<loop> and not by chance takes one letter more wrt "for" since that's for huffmanization and in fact it is thought to be on a much sparser basis to begin with.
        A couple of points.
        • I won't deny Perl 6 gives it a different name, but the Perl 5 parser already knows the 'loop' construct. perly.y has a token 'loop' under which all loop, including the bare block and 4 (!) 'for' constructs. (for my $v (), for $v (), for (), are all parsed separately).
        • The C-style for loop doesn't have three statements inside the parens, it has three expressions - each of which may be empty.
        • Each of the expressions is a different kind of expression (first introduces a scope, second is a boolean expression, third may introduce a scope), but they do fit in other parts of Perls syntax. Of course, you are right in the sense that 'for (;;)' is the only place that requires exactly three expressions.
        • I don't understand the huffmanization remark. Huffmanization means you're minimizing the length of the average program, using tokens that are never a prefix of another token. Making little used tokens longer makes sense, but only if you either the freed up token for something that's used more often, or if the freed token is a prefix of some other token that cannot be replaced. So, what keyword starting with 'for' is used in Perl6? Surely you aren't saying in Perl6 'format' is going to be used more often than 'loop'? ;-)
Re: C-style for loop
by LanX (Saint) on Nov 15, 2008 at 19:48 UTC
    "c-style" is just the name of the loop, it depends what you need...TIMTOWTDI! (*)

    Anyway, with foreach-loop you shouldn't need a new array, since the iterating variable is an alias.

    for $value ( @values) { $value=_clean_cgi_param($value); }

    (*)UPDATE: in many cases foreach-loops are easier to maintain and less redundant than c-style. If you explicitly need to generate a *new* array, prefere map or grep.

    Cheers LanX

    - - - - - Which song???

Re: C-style for loop
by JavaFan (Canon) on Nov 15, 2008 at 19:57 UTC
    Right ?
    Maybe, maybe not. What's better depends on your preferences, and the task at hand.

    About the only thing that's wrong is assuming that without further knowledge, one style is better than the other.

      Indeed. C style loops can't be that bad - otherwise they wouldn't be part of the language, would they? :-)

      Certainly, C style loops are great if you're a good C programmer, and a novice Perl programmer: intuitive, predictable, easy.

      And I guess that described quite a lot of us here at some point in our lives?

      --
      .sig : File not found.

        So everything that is part of Perl is undiluted goodness? How about the two parameter open or the .. operator that is two completely different an unrelated operators depending on context (where the context can be very subtle at times)?

        Perl's for loop is much closer to "intuitive" than C's for loops. Sure, C's for loops are comfortable for a C programmer, but that doesn't make them intuitive, predictable or easy compared with Perl's loops.

        Perl is a different language than C. Just because you can write C (or FORTRAN for that matter) in Perl doesn't mean you should. Embrace the Perly goodness and cast off the C baggage - use Perl for loops unless you really, really, really absolutely can't avoid using a C style loop.


        Perl reduces RSI - it saves typing