Know what your the last evaluated expression actually is.

In Perl, a subroutine or other block structure that returns a value gives back the last evaluated expression, but if you’re not careful you might not recognize what that last evaluation actually is. It’s not necessarily the last statement in the block; it’s just the last one that you actually execute. For this Item, forget about the best practice of using explicit returns. You should do that for precisely the reasons you will see here, but you can’t learn about the problem by avoiding it.

First, dispense with the simple case using the explicit return that denotes the expression to evaluate and to use as the value to pass back. You can only return from a subroutine though:

sub some_sub 
	{
	...
	return $some_number + $another_number;
	}

Your return statement doesn’t have to be the last statement in the subroutine either. Whenever you execute a return, that’s the last expression you evaluate because there is nothing after the if-else in the block:

sub some_sub 
	{
	my $number = shift;
	
	if( $number % 2 ) { return 'odd'  }
	else              { return 'even' }
	}

Move on to something a bit more complicated by not using an explicit return. You can remove those without changing the result:

sub some_sub 
	{
	my $number = shift;
	
	if( $number % 2 ) { 'odd'  }
	else              { 'even' }
	}

The last evaluated expression is the same. The last statement that you’ll execute will be one of those strings, even though you don’t use either of the strings in an operation in the subroutine.

You might recognize the same thing going on with the conditional operator:

sub odd_or_even 
	{
	my $number = shift;
	
	$number % 2 ? 'odd' : 'even'
	}

Those cases are pretty easy to figure out because you would execute at least one of the branches. The case gets more sticky when you might not execute at least one branch.

use 5.010;

say odd(3);
say odd(4);

sub odd 
	{
	my $number = shift;
	
	if( $number % 2 ) 
		{ # not good code
		return 'odd'
		}
	}

The output shows that you get a return value with both odd and even numbers, although one of those values probably isn’t what you expect:

odd
0

Where did the 0 come from? As with any return value, it’s the last evaluated expression. What was that? It was the failed conditional $number % 2. Since there is nothing after the if, you don’t have anything more to evaluate.

Randal Schwartz points out that this leads to the one difference between if( ! ... ) and unless( ... ). The result inside the conditional is different:

use 5.010;

say even_unless(3);
say even_unless(4);

say even_if(3);
say even_if(4);

sub even_if 
	{
	my $number = shift;
	
	if( ! ($number % 2) ) 
		{  # not good code
		return 'even'
		}
	}

sub even_unless 
	{
	my $number = shift;
	
	unless( $number % 2 ) 
		{  # not good code
		return 'even'
		}
	}

The output shows that in this case, the return values are a little different. For an odd number, even_if last evaluates ! ($number % 2) while even_unless last evaluates $number % 2. This means that even_unless always returns true, which isn’t what you want probably:

1
even
        
even

A more common problem is a subroutine that iterates through a list. What’s the last evaluated expression in sum in the this example?

use 5.010;

say "sum is ", sum( 1 .. 1000 );

sub sum 
	{
	my @numbers = @_;
	
	my $sum = 0;
	
	foreach ( @numbers )
		{
		$sum += $_;  # this won't be the return value
		}
	}

Some people might think it’s $sum += $_;, but the foreach actually has to make one final trip back to @numbers to see if it has any more elements. On that final try it realizes it has gone through the entire array, and the result of that check is the empty list. That’s the last thing to happen, so $sum never makes it out of the subroutine because it’s actually the second-to-last evaluated expression.

These same concepts show up in anything blocky-thing that returns values, such as an eval. In this slightly silly and contrived example that guards against a division by zero, you get the wrong answer

use 5.010;

my $divisor = 0;

my $value = eval { if( $divisor ) { 5 / $divisor } };		
say "value from if is [$value]";

The output doesn’t allow you to distinguish between a valid division such as 0/5, and an undefined number such as 5/0:

value from if is [0]

You don’t really need that guard condition because eval handles it for you and gives you back undef when you try to divide by zero:

my $divisor = 0;

my $value = eval { 5 / $divisor };		

Things to remember

  • The last evaluated expression is not necessarily the last statement in a subroutine or block.
  • The last evaluated expression might be part of a conditional or a list iteration.
  • You won’t have these problems when every code path has an explicit return.

One thought on “Know what your the last evaluated expression actually is.”

Comments are closed.