Perl, лениво оценивать строку - PullRequest
7 голосов
/ 03 июня 2010

Рассмотрим следующий код Perl.

#!/usr/bin/perl

use strict;
use warnings;

$b="1";

my $a="${b}";

$b="2";

print $a;

Сценарий явно выводит 1. Я бы хотел, чтобы оно было таким, каким является текущее значение $b.

Каким был бы самый разумный способ в Perl для достижения такой ленивой оценки? Я бы хотел, чтобы ${b} оставался "не замененным" до тех пор, пока не понадобится $a.

Ответы [ 6 ]

15 голосов
/ 03 июня 2010

Мне больше интересно знать, почему вы хотите это сделать. Вы можете использовать различные подходы в зависимости от того, что вам действительно нужно делать.

Вы можете заключить код в кодовую ссылку и оценивать его только тогда, когда вам это нужно:

use strict; use warnings;

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

Вариантом этого будет использование именованной функции в качестве замыкания (это, вероятно, лучший подход, в более широком контексте вашего вызывающего кода):

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

$b = '2';
print_b();

Вы можете использовать ссылку на исходную переменную и разыменовывать ее при необходимости:

my $b = '1';
my $a = \$b;
$b = '2';
print $$a;
4 голосов
/ 03 июня 2010

Perl будет интерполировать строку при запуске кода, и я не знаю, как сделать так, чтобы это не делалось, если не считать форматы (которые ужасны для IMO). Однако вы могли бы изменить «когда код выполняется» на что-то более удобное, заключив строку в подпрограмму и вызвав ее, когда вам нужно интерполировать строку ...

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

Или, вы можете сделать некоторую магию eval, но это немного более навязчиво (вам нужно сделать некоторые манипуляции со строкой, чтобы достичь ее).

3 голосов
/ 04 июня 2010

Как уже упоминали другие, Perl будет оценивать строки только так, как вы их написали, используя eval для вызова компилятора во время выполнения. Вы можете использовать ссылки, как указано в некоторых других ответах, но это меняет внешний вид кода ($$a против $a). Однако, поскольку это Perl, есть способ скрыть расширенные функциональные возможности за простой переменной, используя 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

Вы можете найти документацию для tie, но в двух словах, она позволяет вам определить собственную реализацию переменной (для скаляров, массивов, хэшей или файловых дескрипторов). Таким образом, этот код создает новую переменную $a с реализацией, которая получает или устанавливает текущее значение $b (путем сохранения ссылки на $b внутри). Метод new не является строго необходимым (конструктор на самом деле TIESCALAR), но предоставляется как синтаксический сахар, чтобы избежать необходимости использовать tie непосредственно в вызывающем коде.

(что будет tie my $a, 'Lazy', $b;)

3 голосов
/ 03 июня 2010

То, что вы хотите, это не ленивая оценка, а позднее связывание . Чтобы получить его в Perl, вам нужно использовать eval.

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

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

$number = 42;

eval $x;

print "val is now $val\n";

Имейте в виду, что eval обычно неэффективен, а также методически жесток. Вам почти наверняка лучше использовать решение из одного из других ответов.

1 голос
/ 10 июня 2010

Я бы хотел, чтобы $ {b} оставался "не замененным", пока не понадобится $ a.

Тогда я бы порекомендовал отказаться от интерполяции строк, вместо этого используя sprintf, чтобы вы "интерполировали" при необходимости.

Конечно, на этом основании вы могли бы tie вместе что-то быстрое (иш) и грязное:

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

Работает и с более экзотическими спецификаторами формата. Тьфу.

1 голос
/ 03 июня 2010

Вы хотите сделать вид, что $ a относится к чему-то, что вычисляется при использовании $ a ... Вы можете сделать это, только если $ a на самом деле не скаляр, это может быть функция (как ответ cHao) или, в этом простом случае ссылка на другую переменную

my $b="1";
my $a= \$b;
$b="2";
print $$a;
...