in reply to Re^3: printing unitialized value of the 'do BLOCK' (updated)
in thread printing unitialized value of the 'do BLOCK'

I remember this particular factiod because it has bothered me in the past that the return value of if isn't more clearly documented

Because it's not an expression. Expressions evaluate to a value, not statements. Nothing ever wants the value of an if statement.

Quote perlsub (for subs, but the same applies to do BLOCK):

If no return is found and if the last statement is an expression, its value is returned. If the last statement is a loop control structure like a foreach or a while, the returned value is unspecified. The empty sub returns the empty list.

An if statement isn't a "loop control structure", but more importantly, it's not an expression either. One shouldn't place an if statement as the last statement of a block from which we expect a value.

That said, people do use if statements that way, and the result has been quite predictable. (Update: Mostly predictable. Exception) The last expression evaluated during the course of the processing of the if statement is the resulting value.

my $x = 1; if ($x) { f(); # f() is the last expression evaluated. } else { g(); }
my $x = 0; if ($x) { f(); } else { g(); # g() is the last expression evaluated. }
my $x = 0; if ($x) { # $x is the last expression evaluated. f(); }
my $x = 0; if ($x == 1) { f(); } elsif ($x == 2) ( # $x==2 is the last expression evaluated. g(); }

The same goes for while statements, until statements, and bare loops ({ }). Foreach loops are obviously different (since the iterator is hidden from us).

Updated

Replies are listed 'Best First'.
Re^5: printing unitialized value of the 'do BLOCK'
by haukex (Archbishop) on Dec 17, 2019 at 16:08 UTC
    Because it's not an expression. Expressions evaluate to a value, not statements. Of course, Perl has the oddity that a value can be expected from any statement, but you get weird results if the last statement of a sub or do block is such a statement.

    The reason it bothered me is that the return value of given is explicitly documented:

    When a given statement is also a valid expression (for example, when it's the last statement of a block), it evaluates to: ...

    But of course given is experimental, and I thought it would be nice if it was more clearly defined for the backwards-compatible if.

    One shouldn't rely on that.

    I'm aware of that for for, but not for if. Source?

      Sorry, I have updated the post. It should be clearer and less wrong now. :)

Re^5: printing unitialized value of the 'do BLOCK'
by tobyink (Canon) on Dec 17, 2019 at 18:19 UTC

    Nothing ever wants the value of an if statement.

    A plain if? Probably not. An if/elsif/else statement though?

    my @classifications = map { if ($_ > 0) { 'positive' } elsif ($_ < 0) { 'negative' } else { 'zero' } } @numbers;

    Yes, you can write that using the ternary operator, but if/elsif/else can sometimes be clearer.

      Yes, this is exactly the kind of code that is the reason for my wish to have this behavior of if more clearly defined! Of course, given as a topicalizer would be even cooler, since for ($x) { if (/a/) { "A" } ... } doesn't work for this purpose...

      Nothing ever wants the value of an if statement, even if it has an elsif or else clause. In your example, map is not getting the value of the if statement --there's no such thing-- it's getting the value of the last expression evaluated.

Re^5: printing unitialized value of the 'do BLOCK'
by rsFalse (Chaplain) on Dec 17, 2019 at 17:24 UTC
    Thank you for visual explanations.

    I learned to use 'do { if( ... ){ ... }else{ ... } }' years ago after reading "The do Block" in the book "Intermediate Perl", Chapter 3.
    Few days ago I forgot to use 'do { ...else{ } }' and got unexpected (for myself) results, later I played with minimized case which I posted here.

    And, after examples and explanations, it is interesting that expression inside opposite (= unless) statement is evaluated with negation:
    my $x = 1; unless ($x) { # '$x' is NOT the last expression evaluated, but '!( +$x )' is. f(); }
    Upd.: My comment seems not to be truth. See further discussion - Re^6: printing unitialized value of the 'do BLOCK' proceeded by ikegami.

      I had to go look this up in Intermediate Perl because I was amazed to hear it's evangelized there. I've read the book before but it must not have stood out for me. But you're right, there it is in print. Nevertheless, I think this is not an ideal practice:

      # My memory of the gist of what's in the book, a few hours after havin +g looked it up: my $thing = do { if ($cond1) { "Foo" } elsif ($cond2) { "Bar" } else { "Baz" } };

      In fact it explicitly defies one of the Perl Best Practices assertions (123, I think): Always return via an explicit return. PBP assertions were almost made to be disavowed a year later; some are quite controversial, while others are useful. We may have a useful one here: This may sound totally unrelated because do only has implicit returns, but it's the example code for that assertion I'm latching onto. The code reinforcing why not to use implicit return demonstrates if statements as case-in-point for why an implicit return can be tricky. But do{...} blocks leave you only with a form of implicit return; all the more reason to code for clarity. We always expect intuitively for ternary operators to return a value. And the code above could be written with a ternary pretty easily:

      my $thing = do { ($cond1) ? "Foo" : ($cond2) ? "Bar" : "Baz"; };

      Now we're using a construct designed to predictably return a value, and that anyone who comes to Perl from almost any other C-inspired language could look at and immediately know what's going on. Of course in this case the do{...} block is even unnecessary:

      my $thing = ($cond1) ? "Foo" : ($cond2) ? "Bar" : "Baz";

      But presumably the use-case for the do{...} block would be a little less trivial; maybe something more like this:

      my $thing = do { my $rn = int(rand(6))+1; ($rn == 6) ? "Excellent roll" : ($rn > 3) ? "Pretty good roll." : ($rn == 1) ? "Lousy Roll" : "Meh."; };

      The difference being, we now have a need for a multi-statement construct rolling up to a single return value. Here the do is serving a purpose, but we're still using the ternary instead of the if because it is the most obvious tool for the job (so says me, but I realize that may be debatable).

      I have also found that do{...} blocks have a tendency to be misunderstood by people less experienced with Perl. A year ago I found this error pervasive in our code base, contributed by more than one person, and passed through code review by several others:

      sub agent { my $self = shift; return $self->{_agent} //= do { require config; my $agent_args = $config::config{'agent_args'}; require Agent; return Agent->new($agent_args); }; }

      Yet another contrived example. I'm pretty sure none of the instances I found in our code were actually instantiating a UserAgent, but the problem is there regardless of what purpose it is serving. The point is that this creates a subtle bug; slightly hard to detect, and the side effect of the code actually works. I could call $self->agent->get($url) and it would work every time. The problem is that the line return Agent->new(...) returns from the do block all the way up to the caller of sub agent, bypassing the //= assignment. The $self->{_agent} element will now exist but with an undefined value. The key is there. but the assignment never happens. So every time we call $self->agent we get a new Agent object, not a cached one. Tests may all pass, because the agent is instantiated and works as it should. But (assuming we're doing this caching because it's expensive) performance suffers because the cache never populates.

      So I guess what this long and rambling rant is trying to address is that do{...} is already hard enough for newcomers to the language to get right. We don't need to confuse the issue by relying on an esoteric usage of if statements. That will only serve to embolden those who say Perl is read only and that we should all be using Python.

      I'm all for idiomatic Perl. I just hope we can stick to idioms that are common, clear, not confusing to people. I hate having to continually reassure people that it's not a mistake to continue using it. I appreciate code that doesn't make it harder to defend that position.


      Dave

        Thanks for verbose opinion and bug example!
        I will disagree that an 'if' should require an explicit return. And for me an 'if' statements are sometimes better than ternary, because an 'if' gives an opportunity to skip the 'else' branch, we can't skip it when using ternary op.
        I think that the practice suggested in Intermediate Perl is good, and it goes along with a "don't repeat yourself" principle. And 'given/when' is still an experimental. (edited)

      There's no negation anywhere in that code. $x IS the last expression evaluated when $x is true.

        Thanks for pointing this out!
        I haven't tested much, only tested with plain '1'.
        my $x = 1; print "[$_]" for do{ unless( $x ){ 5 } }, do{ unless( 1 ){ 6 } }, ;
        Output:
        [1] []