Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does Perl return from a subroutine when no return statement is encountered?

Tags:

perl

I encountered this today and thought it prudent to post a Q&A as I couldn't find anything similar.
Feel free to vote-to-close if you find a duplicate of this question.


The following subroutine conditionally returns output; I consider it "clumsy" because it isn't explicit about what is returned to the caller when the conditional is not satisfied:

sub is_multiple_of_three {

    my ( $value ) = @_ ;
    return "$value is a multiple of 3"
      unless $value % 3;
}

A quick rewrite makes short work of clarifying the (more graceful) subroutine's intended behaviour under all circumstances:

sub is_multiple_of_three { 

    my ( $value ) = @_ ;
    return if $value % 3;
    return "$value is a multiple of 3";
}

When calling these both flavours of the subroutine, I was expecting to find some consistency between what both return in list context:

  • a string when the conditional evaluates to true
  • nothing (an empty list) when the conditional evaluates to false

But alas, the behaviour was slightly unexpected:

use strict;
use warnings;
use Data::Printer;
use feature 'say';

my %subs = (
            graceful => sub {
                            my ( $value ) = @_ ;
                            return if $value % 3;
                            return "$value is a multiple of 3";
                        },

              clumsy => sub {
                            my ( $value ) = @_ ;
                            return "$value is a multiple of 3"
                              unless $value % 3;
                        },
           );

for my $name ( keys %subs ) {

    my $sub = $subs{$name};
    say $name;
    my @results = map { $sub->($_) } 1 .. 10;
    p @results;
}

Output

graceful
[
    [0] "3 is a multiple of 3",
    [1] "6 is a multiple of 3",
    [2] "9 is a multiple of 3"
]
clumsy
[
    [0] 1,
    [1] 2,
    [2] "3 is a multiple of 3",
    [3] 1,
    [4] 2,
    [5] "6 is a multiple of 3",
    [6] 1,
    [7] 2,
    [8] "9 is a multiple of 3",
    [9] 1
]

Question

The "graceful" flavour behaves as expected, but why is the "clumsy" sub returning back integers when the conditional is false?

like image 718
Zaid Avatar asked Jan 30 '23 23:01

Zaid


2 Answers

The behaviour is consistent with what is documented in perldoc perlsub

A return statement may be used to exit a subroutine, optionally specifying the returned value, which will be evaluated in the appropriate context (list, scalar, or void) depending on the context of the subroutine call. If you specify no return value, the subroutine returns an empty list in list context, the undefined value in scalar context, or nothing in void context. If you return one or more aggregates (arrays and hashes), these will be flattened together into one large indistinguishable list.

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.


The graceful sub in list context:

  • True : returns the string "$value is a multiple of 3" is returned

  • False : returns an empty list

Which is why there are only three elements in @results; something is added to the array only when the conditional evaluates to true.

The clumsy sub in list context:

  • True : returns the string "$value is a multiple of 3" is returned. No dramas here.
  • False : as no explicit return is encountered, returns the value of the last expression evaluated , $value % 3

So in both cases, the subroutine will return back a value, which is why @results has ten items in it.

like image 120
Zaid Avatar answered Feb 05 '23 16:02

Zaid


For last-expression purposes, foo if bar and if (bar) { foo } are equivalent to bar and foo, and similarly unless is equivalent to or.

like image 31
ysth Avatar answered Feb 05 '23 15:02

ysth