Is it possible to define anonymous subroutines in a hash constructor in Perl?

1.3k Views Asked by At

Is it possible to define anonymous subroutines in a hash constructor in Perl?

I'm trying to do something like this:

my %array = { one   => sub { print "first $_[0]" },
              two   => sub { print "next  $_[0]" },
              three => sub { print "last  $_[0]" }};

$array{$foo}->('thing');

But it isn't working. The code seems to run and compile, but the values in the array are blank. If I do this:

my %array;

$array{'one'}   = sub { print "first $_[0]" };
$array{'two'}   = sub { print "next  $_[0]" };
$array{'three'} = sub { print "last  $_[0]" };

$array{$foo}->('thing');

Then it seems to work fine. So I have a workaround, but it's just bugging me and I wondered if anyone knows whether it's possible and, if so, what the syntax is.

5

There are 5 best solutions below

6
Adam Bellaire On BEST ANSWER

It looks like you're assigning into the hash incorrectly. Using {} constructs an anonymous hash reference, which you assign to a scalar. But you're assigning to a named hash (%array).

You need to assign into a scalar:

my $array = { one   => sub { print "first $_[0]" },
              two   => sub { print "next  $_[0]" },
              three => sub { print "last  $_[0]" }};

$array->{$foo}->('thing');

Or to not use the anon constructor syntax:

my %array = ( one   => sub { print "first $_[0]" },
              two   => sub { print "next  $_[0]" },
              three => sub { print "last  $_[0]" });

$array{$foo}->('thing');
1
codelogic On

That's because in the first case, you're creating a hashref not a hash, what you want is:

my $array;
$array = { one => ... }; # not %array = { .. };
...
$array->{one}->('thing');
0
Yanick On

It's supposed to be parenthesis, not curly braces:

my %array = ( one   => sub { print "first $_[0]" },
              two   => sub { print "next  $_[0]" },
              three => sub { print "last  $_[0]" }});

'{ a => 1, b => 2 }' produces a reference to a hash. So the following would also work:

my $array = { one   => sub { print "first $_[0]" },
              two   => sub { print "next  $_[0]" },
              three => sub { print "last  $_[0]" }};

$array->{one}->('thing');
1
Greg Hewgill On

I spent something like two hours trying to track down this exact braces-vs-parentheses problem in a script not too long ago. If you use the -w switch to Perl, then you'll get a warning "Reference found where even-sized list expected", which at least gives a clue where to look. Today, all Perl scripts should start with:

#!/usr/bin/perl -w

use strict;

Your Perl life will be immeasurably less frustrating.

0
daotoad On

Greg Hewgill is on the right track. Always enable the strict pragma. Also, always enable warnings--but I recommend against using the -w switch in your code.

Instead, take advantage of the use warnings pragma. That way you can selectively disable particular groups of warnings in lexically scoped areas of your code.

For example:

use strict;
use warnings;

foo('bar');
foo();

sub foo {
    no warnings 'uninitialized';

    my $foo = shift || 'DEFAULT';

    print "Foo is $foo\n";
} 

Consistent use of the strict and warnings pragmas will save you hours and hours of time.