Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 "unreplaced" until $a is needed.

like image 872
Mike Avatar asked Jun 03 '10 17:06

Mike


4 Answers

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;
like image 63
Ether Avatar answered Oct 23 '22 12:10

Ether


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.

like image 31
Kilian Foth Avatar answered Oct 23 '22 14:10

Kilian Foth


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

like image 43
cHao Avatar answered Oct 23 '22 13:10

cHao


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

like image 25
Eric Strom Avatar answered Oct 23 '22 12:10

Eric Strom