开发者

Perl, evaluate string lazily

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 "unreplac开发者_如何学Ced" until $a is needed.


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;


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.


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).


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;)


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;


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜