sfpug-perl_coderefs_as_hash_values

This is part of The Pile, a partial archive of some open source mailing lists and newsgroups.



To: sfpug@sf.pm.org
From: "Chris Palmer" <cecibean@bitstream.net>
Subject: [sf-perl] Coderefs as hash values, interpreted at
runtime?
Date: Wed, 21 Nov 2001 00:35:13 GMT

Hello,

I've got a bit of a problem. I have a small program (shortened here for the 
sake of making the point) that could be nifty instead of kludgey, if only I 
could get the syntax right: 

===
#!/usr/bin/perl -w 

use strict; 

my $yes;
my %months = ('Jan' => 31,
             'Feb' => &leap_year(),
             'Mar' => 31,
             'Apr' => 30
             # other months here
            ); 

$yes = 0;
print $months{Feb};
$yes = 1;
print $months{Feb}; 

sub leap_year {
 if ($yes) {
   return 29;
 } else {
   return 28;
 }
}
=== 

You get "2828" as the output instead of the desired "2829". What I want is 
for any getting of the value $months{Feb} to re-invoke leap_year(), taking 
the value of the global boolean $yes into account each time. I had the idea 
of using eval {}, but several attempts to make it go yielded results similar 
to the above. I am also able through several permutations of the above (with 
and without eval) to get the dreaded SCALAR(0x1234) or CODE(0x1234) output. 

If you define $yes to be 1 before %months is defined, you get "2929" as 
output. I think what is happening is that &leap_year is compiled and the 
resulting code assigned to $months{Feb} right away, instead of it being 
reevaluated when invoked, at runtime. 

Any clues? Thanks. :) 

 

===
To: <sfpug@sf.pm.org>
From: David Lowe <dlowe@saturn5.com>
Subject: Re: [sf-perl] Coderefs as hash values, interpreted
at runtime?
Date: Tue, 20 Nov 2001 16:58:04 -0800 (PST)

Chris et. al. -

The short answer: &leap_year() executes a subroutine call - what you want
is to take a reference to the sub like this: \&leap_year.  But this alone
won't solve your problem.

If I may describe your desired data structure in words: a hash, in which
the keys are abbreviated month names, and the values are sometimes
constant scalars and sometimes subroutine calls.

The problem with this is that you're going to need conditional code
everywhere you use it, too, because the data types of the values are
different (sometimes scalars, sometimes subroutine references).

To accomplish the goal (a hash which can be used without special handling
code, but does what you want) you'll need to pick: either all scalars or
all subroutine references.  The latter looks like this:

my %months = ( 'Jan' => sub { 31 },
               'Feb' => \&leap_year,
               'Mar' => sub { 31 },
               'Apr' => sub { 30 },
             );
$yes = 0;
print $months{'Feb'}->(), "\n";
$yes = 1;
print $months{'Feb'}->(), "\n";
sub leap_year {
    return $yes ? 29 : 28;
}

The former is more complex to implement (use tie()) but cleans up the
usage a tad (you can "print $months{'Feb'}" instead of "print
$months{'Feb'}->()").

===

To: sfpug@sf.pm.org
From: Tad McClellan <tadmc@augustmail.com>
Subject: Re: [sf-perl] Coderefs as hash values, interpreted
at runtime?
Date: Tue, 20 Nov 2001 19:24:15 -0500

On Wed, Nov 21, 2001 at 12:35:13AM +0000, Chris Palmer wrote:

> my $yes;
> my %months = ('Jan' => 31,
>              'Feb' => &leap_year(),
                                  ^^
                                  ^^ parens always mean "call the sub now"

   'Feb' => \&leap_year,    # take a ref rather than call the sub


>              'Mar' => 31,
>              'Apr' => 30
>              # other months here
>             ); 
> 
> $yes = 0;
> print $months{Feb};

   print $months{Feb}->();  # deref and call it

> $yes = 1;
> print $months{Feb}; 

   print $months{Feb}->();  # deref and call it


> sub leap_year {
>  if ($yes) {
>    return 29;
>  } else {
>    return 28;
>  }
> }


You might want to make _all_ of the values in %months be code refs,
then you won't have to special case February:

my %months = ('Jan' => sub {31},
              'Feb' => \&leap_year,
              'Mar' => sub {31},
              'Apr' => sub {30}
              # other months here
            );


You might also want to pass $yes as an argument. Now that they
are all code refs, you don't need for it to be global.


   print $months{Feb}->($yes);

   sub leap_year {
     if ($_[0]) {
     ...

===

To: sfpug@sf.pm.org
From: Paul Makepeace <Paul.Makepeace@realprogrammers.com>
Subject: Re: [sf-perl] Coderefs as hash values, interpreted
at runtime?
Date: Tue, 20 Nov 2001 17:06:48 -0800

On Wed, Nov 21, 2001 at 12:35:13AM +0000, Chris Palmer wrote:
> Hello,
> 
> I've got a bit of a problem. I have a small program (shortened here for the 
> sake of making the point) that could be nifty instead of kludgey, if only I 
> could get the syntax right: 
> 
> ===
> #!/usr/bin/perl -w 
> 
> use strict; 
> 
> my $yes;
> my %months = ('Jan' => 31,
>             'Feb' => &leap_year(),
>             'Mar' => 31,
>             'Apr' => 30
>             # other months here
>            ); 
> 
> $yes = 0;
> print $months{Feb};
> $yes = 1;
> print $months{Feb}; 

There's no way directly to do what you want without doing one of two
things, checking for the existence of a CODE ref or tie'ing the hash
and doing the CODE ref test inside the tie'd package.

# note \ to get CODE ref
	Feb => \&leap_year,
...

sub leap_year {
    my $y = 1996; #shift || (localtime)[5];
    28 + (($y % 4) == 0 && ($y % 100) != 0)
}

my $month = 'Feb';

my $m = $months{$month};
my $days = ref $m ? &$m : $m;

print "$month has $days days\n";

===

To: sfpug@sf.pm.org
From: David Wheeler <david@Wheeler.net>
Subject: Re: [sf-perl] Coderefs as hash values, interpreted
at runtime?
Date: 20 Nov 2001 17:10:25 -0800

AFAICT, there's no simple way to do this without using coderefs. Here's
my solution:

#!/usr/bin/perl -w

use strict;
my $yes;
my %months = ('Jan' => sub { 31 },
	      'Feb' => \&leap_year,
	      'Mar' => sub { 31 },
	      'Apr' => sub { 30 }
	      # other months here
           );
$yes = 0;
print &{$months{Feb}};

$yes = 1;
print &{$months{Feb}};

sub leap_year { $yes ? 29 : 28 }

However, you might be better off using a CPAN module.

===



the rest of The Pile (a partial mailing list archive)

doom@kzsu.stanford.edu