views:

154

answers:

6

Consider the following Perl code.

#!/usr/bin/perl

use strict;
use warnings;

$b="1";

my $a="${b}";

$b="2";

print $a;

The script obviously outputs 1. I would like it to be whatever the current value of $b is.

What would be the smartest way in Perl to achieve lazy evaluation like this? I would like the ${b} to remain "unreplaced" until $a is needed.

+4  A: 

Perl will interpolate a string when the code runs, and i don't know of a way to make it not do so, short of formats (which are ugly IMO). What you could do, though, is change "when the code runs" to something more convenient, by wrapping the string in a sub and calling it when you need the string interpolated...

$b = "1";
my $a = sub { "\$b is $b" };
$b = "2";
print &$a;

Or, you could do some eval magic, but it's a bit more intrusive (you'd need to do some manipulation of the string in order to achieve it).

cHao
+1  A: 

You wish to pretend that $a refers to something that is evaluated when $a is used... You can only do that if $a is not truly a scalar, it could be a function (as cHao's answer) or, in this simple case, a reference to the other variable

my $b="1";
my $a= \$b;
$b="2";
print $$a;
leonbloy
+11  A: 

I'm more interested in knowing why you want to do this. You could use a variety of approaches depending on what you really need to do.

You could wrap up the code in a coderef, and only evaluate it when you need it:

use strict; use warnings;

my $b = '1';
my $a = sub { $b };
$b = '2';
print $a->();

A variant of this would be to use a named function as a closure (this is probably the best approach, in the larger context of your calling code):

my $b = '1';
sub print_b
{
    print $b;
}

$b = '2';
print_b();

You could use a reference to the original variable, and dereference it as needed:

my $b = '1';
my $a = \$b;
$b = '2';
print $$a;
Ether
+1 for the good selection of techniques, but for your second subroutine to work, you need to define 'my $b' before you define the subroutine, otherwise it binds to the global $b instead.
Andy Mortimer
@Andy: good point!
Ether
@Ether, I was trying to come up with a way of doing a quick and dirty string substition. Your coderef suggestion was very good, thanks
Mike
+1  A: 

What you want is not lazy evaluation, but late binding. To get it in Perl, you need to use eval.

my $number = 3;
my $val = "";

my $x = '$val="${number}"';

$number = 42;

eval $x;

print "val is now $val\n";

Be advised that eval is usually inefficient as well as methodically atrocious. You are almost certainly better off using a solution from one of the other answers.

Kilian Foth
+3  A: 

As others have mentioned, Perl will only evaluate strings as you have written them using eval to invoke the compiler at runtime. You could use references as pointed out in some other answers, but that changes the way the code looks ($$a vs $a). However, this being Perl, there is a way to hide advanced functionality behind a simple variable, by using tie.

{package Lazy;
    sub TIESCALAR {bless \$_[1]}         # store a reference to $b
    sub FETCH {${$_[0]}}                 # dereference $b
    sub STORE {${$_[0]} = $_[1]}         # dereference $b and assign to it
    sub new {tie $_[1] => $_[0], $_[2]}  # syntactic sugar
}

my $b = 1;
Lazy->new( my $a => $b );   # '=>' or ',' but not '='

print "$a\n";  # prints 1
$b = 2;
print "$a\n";  # prints 2

You can lookup the documentation for tie, but in a nutshell, it allows you to define your own implementation of a variable (for scalars, arrays, hashes, or file handles). So this code creates the new variable $a with an implementation that gets or sets the current value of $b (by storing a reference to $b internally). The new method is not strictly needed (the constructor is actually TIESCALAR) but is provided as syntactic sugar to avoid having to use tie directly in the calling code.

(which would be tie my $a, 'Lazy', $b;)

Eric Strom
+1  A: 

I would like the ${b} to remain "unreplaced" until $a is needed.

Then I'd recommend eschewing string interpolation, instead using sprintf, so that you "interpolate" when needed.

Of course, on this basis you could tie together something quick(ish) and dirty:

use strict;
use warnings;

package LazySprintf;

# oh, yuck
sub TIESCALAR { my $class = shift; bless \@_, $class; }
sub FETCH     { my $self = shift; sprintf $self->[0], @$self[1..$#$self]; }

package main;

my $var = "foo";
tie my $lazy, 'LazySprintf', '%s', $var;

print "$lazy\n"; # prints "foo\n"
$var = "bar";
print "$lazy\n"; # prints "bar\n";

Works with more exotic format specifiers, too. Yuck.

pilcrow
Totally awesome 'yuck' :-)
pst