1

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.

brian d foy
  • 129,424
  • 31
  • 207
  • 592
Jeremy Bourque
  • 3,533
  • 1
  • 21
  • 18

5 Answers5

11

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');
Adam Bellaire
  • 108,003
  • 19
  • 148
  • 163
  • 2
    Of course, this would even be more correct, if we called that variable %hash instead of %array – innaM Jan 30 '09 at 22:13
  • @Manni: Good point. While hashes are technically also called "associative arrays" in practise every Perl programmer I know uses "array" to mean exclusively plain arrays, and "hash" to mean hashes. – Adam Bellaire Jan 30 '09 at 22:16
  • Ack, I just britishized the word "practice." I don't know where that came from. – Adam Bellaire Jan 30 '09 at 22:16
  • "practice" is the noun, "practise" the verb. – Sam Kington Jan 31 '09 at 17:06
  • So it was not only britishized but ungrammatical. Here in America, "practice" is both the noun and the verb. – Adam Bellaire Jan 31 '09 at 17:10
  • @Adam: In the English speaking world :P the easy way to remember which is which is to think of advise/advice. License/license, practise/practice et. al. all follow the same rules. ;-) – RET Feb 13 '09 at 10:30
6

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');
codelogic
  • 71,764
  • 9
  • 59
  • 54
4

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.

daotoad
  • 26,689
  • 7
  • 59
  • 100
3

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.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 1
    I'd recommend use warnings over -w, as the former can be turned off in a lexical scope when needed. – friedo Jan 31 '09 at 03:22
2

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');
Yanick
  • 1,250
  • 8
  • 9