2

Consider the following perl script:

use strict;
use warnings;
use Data::Dumper;

# Expects an array reference and an undef replacement value
sub undef_to_value(\@$) {
    my $array_ref = shift(@_);
    my $default   = shift(@_);

    # Map each element in the array referenced by $array_ref to...
    #   If the element is defined, itself
    #   Otherwise, the $default value
    return map {
        defined($_) ? $_ : $default
    } @{ $array_ref };
}


my %grades = ("Alice" => 100, "Bob" => 85, "Carol" => 92);
my @students = ("Alice", "Bob", "Eve");

The following code works as expected:

my @student_grades = @grades{ @students };
@student_grades = undef_to_value(@student_grades, 0);

print Dumper(\@student_grades);
# $VAR1 = [
#           100,
#           85,
#           0
#         ];

However, trying to pass a hash slice results in Type of arg 1 to main::undef_to_value must be array (not hash slice):

my @student_grades = undef_to_value( @grades{ @students }, 0 );

How is it that a hash slice can be coaxed into an array with assignment, but not during a subroutine call?
Is there any way to get the failing example to work as a single assignment?

4
  • Which Perl version are you using? Commented Feb 7, 2017 at 17:31
  • defined($_) ? $_ : $default is usually written $_ // $default. Also note that subroutine prototypes should be avoided because, as you have seen, they almost certainly don't do what you want them to do. They are intended for writing library functions that emulate the behaviour of built-in Perl operators, and that is certainly not what you are doing here. Commented Feb 7, 2017 at 18:07
  • // has been available since 5.10. Commented Feb 7, 2017 at 18:09
  • I'll have to remember //, it seems useful. As for avoiding prototypes, the code I'm working on will likely be used and modified by people who aren't terribly perl savvy. Prototypes help me avoid a lot of the guesswork on what kind of edge cases I need to check for. Commented Feb 7, 2017 at 19:57

2 Answers 2

4

How is it that a hash slice can be coaxed into an array with assignment

It's not. A hash slice in list context evaluates to a number of scalars, and the list assignment operator is perfectly happy with that.

In other words,

@L{qw( a b )} = @R{qw( a b )};

is equivalent to

($L{a}, $L{b}) = ($R{a}, $R{b});

As your comment says, undef_to_value expects an array reference. The prototype provides this if you provide an array, but you are providing" an hash slice instead. That's not a type a variable, so you can't take a reference to it[1].

Just accept scalars instead:

sub undef_to_value {
    my $default = shift(@_);
    return map { $_ // $default } @_;
}

my @student_grades = undef_to_value(0, @grades{ @students });

Of course, you could simply use

my @student_grades = map { $_ // 0 } @grades{ @students };

  1. \@h{LIST} is equivalent to map { \$_ } @h{LIST}.
Sign up to request clarification or add additional context in comments.

3 Comments

Looks like I need to brush up on my data types. I didn't realize there was such a difference between arrays and lists. I incorrectly thought that slices returned arrays, but it appears they return lists instead. Sadly, it doesn't appear that lists are a valid data type for subroutine prototypes.
There's no list data type. List usually refers to scalars on the stack or the comma operator.
Re "I incorrectly thought that slices returned arrays", Like every other sub and operator, they return a scalar in scalar context and zero or more scalars in list context. (There are exceptions where an array or hash is actually passed to an operator instead of being evaluated. This kinda work like the \@ prototype. An example is @a = ....)
0

You can fool the prototype by referencing and then dereferencing an arbitrary list

undef_to_value( @{[@grades{ @students }]}, 0 );

but this will only modify a copy of your input, so it's not that helpful.

undef_to_value( @{[@grades{ @students }]}, 0 );
print Dumper([@grades{@students}]);
---
$VAR1 = [
    100,
    85,
    undef
        ];

Fortunately, your undef_to_value function also returns the set of updated values, so you can say

@grades{@students} = undef_to_value( @{[@grades{ @students }]}, 0 );
print Dumper(\%grades);
---
$VAR1 = {
          'Bob' => 85,
          'Carol' => 92,
          'Eve' => 0,
          'Alice' => 100
        };

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.