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

Help - I'm a C programmer. My code is rarely "cool". I was hoping you could turn this very functional code into something more....I don't know...clever, elegant, cool? I pass a hash table to a function. This function checks the hash out every which way, and could make changes to it in a half dozen different places. The thing is, if a change is made, I have to do it all over again. And again. Until finally no more changes are made to the hash.

I use a global flag. If a change is made in the function, the flag is set to one and I know to repeat the iteration. Here's the code (ignore the %seen, it help me avoid unwanted recursion):

# Call the Find_First function while($repeat_flag){ $repeat_flag = 0; %SEEN = (); # reset hash while(($k, $v) = each %GRAMMAR) { &Find_First($k, $v); } }

Like I said, this works fine. But can I make it more, well, you know...
Thanks, -tl

Replies are listed 'Best First'.
Re: Help - I'm a C programmer
by Masem (Monsignor) on Jun 17, 2001 at 16:22 UTC
    You should always avoid globals at all costs; it's just bad programming in any language.

    I'm not sure what you are trying to do with your code, but lets assuming that the function that you evaluate and manipulate your hash is "change_hash". "change_hash" should return a value, 1 if something changed, or 0 if no changes were made. With that in mind, your basic code should look something like this:

    while ( change_hash( \%hash ) ) {}

    Within change_hash, you appear to be looking at all the elements, so one way to write this as to return what you need is as follows:

    sub change_hash { my $hashref = shift; my $returnflag = 0; foreach my $key ( keys %$hashref ) { if ( change_needed( $key, $$hashref{ $key } ) ) { $returnflag = 1; # do change here } } return $returnflag; }

    Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
      Excellent reply. Although I'd like to say that:
      You should always avoid globals at all costs; it's just bad programming in any language.
      Is wrongheadedness like most blanket statements in programming ("never use a goto!" "always enter loops at the top and exit at the bottom!" etc..).

      You should avoid globals<super>*</super> wherever possible, but not go through great pains and contortions to do so. Sometimes they're more appropriate than passing parameters to every function in a system to handle every possible need that may require them. For our C programmer, errno is an example where having a variable that has a "global" scope is much more appropriate than having every function which might do I/O receive it (or a pointer) as a parameter and pass it around during exception handling.

      <super>*</super>I'm including file-scoped lexicals and package variables in this category and not the ${special_character} variables. Those, for the most part, are evil.

        The thing about global variables is not the way they work; it's generally the problem with namespace collisions.

        There are times when you do need something similar to a global variable functionality; in languages where namespace collisions can be easily avoided, such as C++, Java, Perl, or the like, you should never define a global variable, instead creating one at an Object or Package scope. Unforunately, you can't do the same for C; the best you can do is create a special variable with a very descriptive name to act as a global, such that the chance for namespace collision is low. Even with something like errno, that ought to be stored away in some non-global area to prevent some third party code overwriting it unintentionally before you have a chance to use it.


        Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
        I agree with clintp's comments about globals.

        For me, the amount of globals I use depends very much on the type of code I am writing. (See useful discussion on styles of code @ Why I like functional programming).

        For e.g. an object class, I would expect no globals to be used in the implementation.

        For e.g. an event loop (driven my user input), where you have large numbers of entry points into the code, you don't want to handle the passing of the state around everywhere, specially when the state gets large.

        The "globals" though should be carefully restricted to the smallest possible scope so that they don't pollute. It should be rare that the scope is "main", for any Packages in Perl.

        Taking the errno example, I took the view recently that it was better to hide the implementation of errors and provide an error() function that would provide the most recent error, rather than expose the implementation of errors within the package. Now I can change the implementation later without breaking existing user code.
        --
        Brovnik

      Would you point me in the right direction to understand the %$ and the $$? I looked in my "Programming Perl" book but didn't find anything.

      Thanks,
      tl

        The %$hashref construct is just a shortcut for %{ $hashref }. $$hashref{$key} is similar, just takes a single value from hash.

        -mk
Re: Help - I'm a C programmer
by VSarkiss (Monsignor) on Jun 17, 2001 at 21:05 UTC
    I assume your Find_First function modifies $repeat_flag according to some criteria. That's really the problem: it's unclear from this snippet that the subroutine call is controlling the loop. So here's one way to make it clearer.
    OUTER: while (1) { while (($k, $v) = each %GRAMMAR) { last OUTER if Find_First($k, $v); } }
    I may have the "sense" of the test backwards; depending on what else you're doing, unless might make more sense. Also, OUTER is not really a descriptive label outside of this example. Pick something that makes the last read better.

    In general, I've found that using a variable to control a loop is a habit from other programming languages. Given Perl's powerful loop constructs, if you find yourself using a loop control variable, you may want to see if there's a way to avoid it. That's not an absolute; sometimes it's clearer to just say "while ($keepgoing)" or something, but often a last or next to a well-chosen label can be better. There's an excellent example in the perlsyn document regarding nested loops just like this.

    HTH