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

Fellow monasterians,

This is something very elementary that bugs me everytime I code it, and thought I should nip it here an now. I guess it's more about verbose-looking code than anything and wondering if this is just how things are in Perldom. Here goes:

#!/usr/bin/perl use strict; my $foo = 1; if ($foo) { my $bar = "true"; } print $bar;

Of course, this bit of code results in a strict error, so declare $bar ahead of the conditional...

my $bar; if ($foo) { $bar = "true"; } print $bar;

But now I have a global. So I add braces to reign it in...

{ my $bar; if ($foo) { $bar = "true"; } print $bar; }

Which is usually how I code it, but it seems "ugly." Yes, I know that is one of the features of Perl, and this might all sound like whining, but is there a better way? I guess what I'm thinking should be possible is:

if ($foo) { my $bar = "true"; } print $bar;

Thanks. I feel better having finally asked this.


—Brad
"Don't ever take a fence down until you know the reason it was put up. " G. K. Chesterton

Replies are listed 'Best First'.
Re: A cleaner way of scoping variables
by Arunbear (Prior) on Aug 09, 2004 at 13:08 UTC
    Use indentation to remove ugliness:
    { my $bar; if ($foo) { $bar = "true"; } print $bar; }
    I suppose you could hide $bar in a subroutine:
    sub test { my $arg = shift; my $bar; if($arg) { $bar = "true"; } return $bar; } my $foo = 1; print test($foo);
    But that's an extreme solution.

    Update:
    This approach eliminates the global and the curlies:

    use strict; my $foo = 1; if ($foo) { package Strange; our $bar = "true"; } print $Strange::bar || "false";

    :-)
      Excellent ideas!

      I humbly submit that the way perl does it is correct - the original op just needs to get used to doing it the "right" way. I can't see how variable scoping in perl is incorrect in any way.

        Agreed; it was just a little brainstorming exercise.

      Yes, thank you. That last example was especially thought-provoking, though probably not practical in real life conditions. But interesting.


      —Brad
      "Don't ever take a fence down until you know the reason it was put up. " G. K. Chesterton
Re: A cleaner way of scoping variables
by Aristotle (Chancellor) on Aug 09, 2004 at 13:15 UTC

    There is no answer in the general case of how that naked block could be avoided, but I find that do can often help group things like this better. (In this example, you could use a ternary so the do isn't even necessary, but I'll humour you and assume it stands for a more complex piece of code.)

    { my $bar = do { if( $foo ) { "true" } else { undef } }; print $bar; }

    Makeshifts last the longest.

Re: A cleaner way of scoping variables
by adrianh (Chancellor) on Aug 09, 2004 at 13:13 UTC
    my $bar = $foo ? "true" : undef; ?
Re: A cleaner way of scoping variables
by ihb (Deacon) on Aug 09, 2004 at 13:13 UTC

    Adding a bare block is a common technique to scope tighter. I don't understand the question really. The point of having the my inside the if block is so that $bar won't be visible outside of the block. Do you want another my-like keyword that makes a variable exist only "below", regardless of scope?

    Is your question specifically about this kind of if blocks? If you, so you perhaps what to look at the ternary ?: operator which is an blockless if-else.

    ihb

    Read argumentation in its context!

      Yes, my intent, magically and apparently erroneously, was to use it "below." I do use the ternary operator often, but my example, as alluded to by other responders, is really simplied and in reality goes beyond the range of the clever ternary approach.

      —Brad
      "Don't ever take a fence down until you know the reason it was put up. " G. K. Chesterton
Re: A cleaner way of scoping variables
by Chady (Priest) on Aug 09, 2004 at 13:26 UTC

    If $bar is temporary, and your main worry is about ending up declaring a lot of globals for temporary usage, then consider changing your code.

    I know that your example is simplified for the sake of posting, but nevertheless, in your example you can throw away $bar completely.

    if ($foo) { print "true"; }

    But if you want to use the value a little farther below it probably means that you need that variable, and hence you should declare it, or maybe you should group together the parts of code that are working on the same temporary variable so that you can scope tightly.

    just my 2 cents.


    He who asks will be a fool for five minutes, but he who doesn't ask will remain a fool for life.

    Chady | http://chady.net/
      Another consideration along this lines and that of using subroutines is general code style/design.

      If you break your code up more into subrotines, methods, modules, classes etc. you still have the problem though it is considerably less relevent.

      i.e. if your whole code block (sub/method) is already naturally defined as 10-20 lines of code, then the fact $bar is scoped slightly longer than you want, hardly matters since it is going to go right out of scope a few lines later anyway when your subroutine/method ends.

      I know my preference may vary from others, but when I get my code reduced to classes and a "driver" script that just says:

      do_step_1
      do_step_2
      do_step_3
      exit_gracefully

      I am quite happy with it. Debugging generally very quickly gets you to 10-20 lines of code making it much easier to figure out. Then, if you are using global variables in this scenario, you deserve whatever you get...

Re: A cleaner way of scoping variables
by Crackers2 (Parson) on Aug 09, 2004 at 15:50 UTC

    But if $bar is still in scope after the if, how is this any different from just declaring it right before the block ?

    In your example you use the extra braces to limit the scope of your variable to prevent it from being a global. If your "preferred" code would be allowed, where do you think $bar should go out of scope? i.e. :

    if ($foo) { my $bar = "true"; } print $bar; print "again" . $bar; #-- does this still work <cut 500 lines> print "and again" . $bar; #-- what about this?

    If all those prints work, your code is essentially the same as when you just declare $bar before the if block. If not, at what point does $bar go out of scope ?

    Another point: what happens in your code if the condition is false? Would you want a runtime error (because $bar is not declared in that case) ? Or are you thinking more of declaring $bar in both the if and else branch?

    There's one possible case where I could see your suggestion being somewhat useful, namely something like this:

    my $bar = "hello"; #-- global sub mysub { if (condition) { my $bar = "goodbye"; } print $bar; }

    i.e. where the sub-local variable would obscure an already-existing global. But this is both ugly and bad design.

    So to summarize, my main question to you is: when would $bar go out of scope in the code you propose

      If all those prints work, your code is essentially the same as when you just declare $bar before the if block.

      Almost, but not quite. You could have code between the start of the if block and the $bar declaration that wasn't allowed to use $bar if using what the OP was asking for. I'm not saying that this is something I want, because as you say, it would be like a global except you can't use it above the statement. But I think that's the detail that the OP was thinking about.

      ihb

      Read argumentation in its context!

      Thanks much Crackers2, excellent points all well taken. Your last question is a zinger and I really can't answer that. I do like your suggestion of keeping in within a sub, but other than that, I think my original OP was a bit presumptuous in trying to suggest something basically impossible in Perl (or any other language for that matter).

      —Brad
      "Don't ever take a fence down until you know the reason it was put up. " G. K. Chesterton
Re: A cleaner way of scoping variables
by ambrus (Abbot) on Aug 09, 2004 at 13:29 UTC

    There is a way you can avoid declaring $bar once and assining it an other time:

    use strict; my $foo = 1; { my $bar = do { if ($foo) { "true"; } }; print $bar; }

    You still need a pair of braces around the whole construct. If you want $bar to be local, you need a block, how else would perl know where the variable is local.

    The block is not ugly imo. I often use blocks in perl just to declare local variables. There is a way to avoid such blocks, but that involves changing programming style: you have to write short (50 lines most) subroutines that do one thing and do it well, and you can declare local variables at the top of each subroutine, with one my() statement perl sub (and preferable at most 7 variables). This is a good programming style in C imo (see Documentation/CodingStyle in the linux source), but doesn't always worth in Perl.

    This is a bit OT, but note that I write the curlies of a block inline if the block contains only one line of code.

      This code has a problem: if the condition is false, the condition is the last expression evaluated in the do block, so whatever the condition evaluates to becomes the result of the do block.

      $ perl -le'print do { "x" if 1 }' x $ perl -le'print do { "x" if 0 }' 0

      In your case, the value of $foo is assigned to $bar if $foo is false. This could be particularly nasty because you'll never notice if you don't run tests with $foo being 0 or the empty string rather than undefined.

      You must make sure that your do blocks always evaluate to an intended result. In this case, you have to add an else { undef } clause.

      Makeshifts last the longest.

        You must make sure that your do blocks always evaluate to an intended result. In this case, you have to add an else { undef } clause.

        Indeed.

        In your case, the value of $foo is assigned to $bar if $foo is false.

        Perl is a bit wierd about the return value of a loop of which the body never runs. This code:

        #!/usr/bin/perl -w my $true = "foo"; my $false = "0"; my $dc = "bar"; warn do { $dc if $false }; warn do { $dc unless $true }; warn do { $dc while $false }; warn do { $dc until $true }; warn do { if ($false) { $dc } }; warn do { unless ($true) { $dc } }; warn do { while ($false) { $dc } }; warn do { until ($true) { $dc } }; warn do { $dc if "0" }; warn do { $dc unless "foo" }; warn do { $dc while "0" }; warn do { $dc until "foo" }; warn do { if ("0") { $dc } }; warn do { unless ("foo") { $dc } }; warn do { while ("0") { $dc } }; warn do { until ("foo") { $dc } }; __END__
        prints:
        Useless use of private variable in void context at a line 9. Useless use of private variable in void context at a line 10. Useless use of private variable in void context at a line 13. Useless use of private variable in void context at a line 14. Useless use of private variable in void context at a line 21. Useless use of private variable in void context at a line 22. 0 at a line 7. foo at a line 8. 0 at a line 9. foo at a line 10. 0 at a line 11. foo at a line 12. 0 at a line 13. foo at a line 14. 0 at a line 15. foo at a line 16. 0 at a line 17. Warning: something's wrong at a line 18. 0 at a line 19. Warning: something's wrong at a line 20. Warning: something's wrong at a line 21. Warning: something's wrong at a line 22.
Re: A cleaner way of scoping variables
by tilly (Archbishop) on Aug 09, 2004 at 21:10 UTC
    My solution is to start putting all code into functions early, and then break out subfunctions whenever it makes sense. That way random code is always in a reasonably small lexical scope, and I don't have to care much if a random lexical has a slightly larger scope than I'd ideally want.
      Thanks tilly, I'll file that under "good practices." Cheers.

      —Brad
      "Don't ever take a fence down until you know the reason it was put up. " G. K. Chesterton
Re: A cleaner way of scoping variables
by davido (Cardinal) on Aug 09, 2004 at 15:55 UTC

    Two examples that eliminate the need for $bar entirely.

    my $foo = 1; print +($foo)? "True" : "";

    Or, if it's more involved than that...

    my $foo = 1; print do{ if( $foo ) { "True" } }

    In your last example, you asserted that it would be nice if $bar were accessible outside of the if(){} block. But you previously stated that you didn't want to use a global variable (in this case what you really meant was a lexical scoped to package level). The problem is that if $bar is accessible outside of the if(){} block, it's no different from a package-scoped lexical. You don't want your lexicals leaking outside of the block they're defined in; that defeats the purpose of a lexical variable.


    Dave

      davido, thanks. Your last paragraph pretty much sums up what I've learned and take away from this thread. See my comments to Crackers2 above.

      —Brad
      "Don't ever take a fence down until you know the reason it was put up. " G. K. Chesterton
Re: A cleaner way of scoping variables
by TomDLux (Vicar) on Aug 10, 2004 at 01:31 UTC
    1. If it's short, use the trailing if ...
      my $bar = "true" if $foo;
    2. If it's short and you have two options, use the trinary operator ...
      my $bar = $foo ? "Winner!!!" : "Please play again.";
    3. If it's a longer block, it doesn't matter if you have one declaration line at the front. You can even use default values if you want.
      my ( $foo, $bar, $baz, $wumpus ); # or # my ( $foo, $bar, $baz, $wumpus ) = ( 5555, "Blossom Garden Rd", "San + Jose, CA", 95123 ); if ( $tiddly_rump ) { $foo .... $bar .... $baz .... $wumpus .... }
      That's not so bad, is it?

    --
    TTTATCGGTCGTTATATAGATGTTTGCA

      If it's short, use the trailing if ...
      my $bar = "true" if $foo;
      Nooooooooooooooooo!

      grep 5.8.1+ perlsyn for 'Here be dragons'.

        So greppeth perlsyn:

        NOTE: The behaviour of a my statement modified with a statement modifier conditional or loop construct (e.g. my $x if ...) is undefined. The value of the my variable may be undef, any previously assigned value, or possibly anything else. Don't rely on it. Future versions of perl might do something different from the version of perl you try it out on. Here be dragons.