Возвращение лениво вычисляемого скаляра в perl

Я пытаюсь добавить некоторые функциональные возможности в нашу кодовую базу, используя связанные скаляры.

У нас есть функция, которая указана для возврата скаляров. Я думал, что мог бы добавить некоторые функции в систему, связав эти скаляры перед их возвратом, но похоже, что метод FETCH вызывается непосредственно перед возвратом, что приводит к возвращению несвязанного скаляра.

Есть ли способ обойти это?

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

use strict;
use warnings;
main();

sub GetThing{
    my $thing;
    tie $thing, 'mything', @_;
    return $thing;
}

sub main {
    my %m;
    $m{pre} = GetThing('Fred');
    print "1\n";
    print $m{pre};
    print "2\n";
    print $m{pre};
    print "3\n";
}


package mything;
require Tie::Scalar;

my @ISA = qw(Tie::StdScalar);

sub TIESCALAR {
    my $class  = shift;
    bless {
        name    => shift || 'noname',
    }, $class;
}

sub FETCH {
    my $self = shift;
    print "ACCESS ALERT!\n";
    return "    NAME: '$self->{name}'\n";
}

Желаемый результат:

1
ACCESS ALERT!
    NAME: 'Fred'
2
ACCESS ALERT!
    NAME: 'Fred'
3

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

--Buck

4 ответа

Решение

Как сказал DVK, связывание применяется к контейнерам, поэтому бесполезно для возвращаемых значений.

Для этого вы используете перегрузку. Пример (приведены не все возможные перегруженные операции; см. http://perldoc.perl.org/overload.html):

use strict;
use warnings;
main();

sub GetThing{
    my $thing;
    $thing = "mything"->new(@_);
    return $thing;
}

sub main {
    my %m;
    $m{pre} = GetThing('Fred');
    print "1\n";
    print $m{pre};
    print "2\n";
    print $m{pre};
    print "3\n";
}


package mything;
use overload 'fallback' => 1, '""' => 'FETCH';

sub new {
    my $class = shift;
    bless {
        name    => shift || 'noname',
    }, $class;
}

sub FETCH {
    my $self = shift;
    print "ACCESS ALERT!\n";
    return "    NAME: '$self->{name}'\n";
}

Как уже упоминалось в других ответах, tie применяется к контейнерам, а не к значениям, поэтому невозможно связать связанную переменную с другой переменной и сохранить связанные свойства.

Поскольку назначение отсутствует, необходимо передать контейнер в GetThing рутина. Вы можете сделать это по ссылке следующим образом:

use strict;
use warnings;
main();

sub GetThing{
    tie ${$_[1]}, 'mything', $_[0];
}

sub main {
    my %m;
    GetThing('Fred' => \$m{pre});
    print "1\n";
    print $m{pre};
    print "2\n";
    print $m{pre};
    print "3\n";
}


package mything;
require Tie::Scalar;

my @ISA = qw(Tie::StdScalar);

sub TIESCALAR {
    my $class  = shift;
    bless {
        name    => shift || 'noname',
    }, $class;
}

sub FETCH {
    my $self = shift;
    print "ACCESS ALERT!\n";
    return "    NAME: '$self->{name}'\n";
}

который производит правильный вывод.

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

use strict;
use warnings;
main();

sub GetThing{
    return mything->new( shift );
}

sub main {
    my %m;
    $m{pre} = GetThing('Fred');
    print "1\n";
    print $m{pre};
    print "2\n";
    print $m{pre};
    print "3\n";
}


package mything;

sub new {
    my $class  = shift;
    bless {
        name    => shift || 'noname',
    }, $class;
}

use overload '""' => sub {   # '""' means to overload stringification
    my $self = shift;
    print "ACCESS ALERT!\n";
    return "    NAME: '$self->{name}'\n";
};

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

Во-первых, точный метод выполнения того, что вы предлагаете, технически невозможен:

  1. Связанные переменные привязаны к самой переменной, а не к ее значению.

  2. В Perl возвращаемые значения подпрограммы возвращаются по значению, то есть вы берете значение, переданное в return, получить доступ к нему (в вашем случае доступ к связанной переменной и вызов FETCH в процессе) - а затем скопируйте это значение! Это означает, что то, что получает вызывающая сторона, является скалярным VALUE, а не скалярной переменной (связанной или несвязанной).

Короче говоря, ваша путаница, по-видимому, вызвана смешением переменных (мест в таблице символов программы) и значений, хранящихся в этих переменных.


Во-вторых, вам было неясно, чего именно вы пытаетесь достичь, поэтому трудно предложить, как добиться того, чего вы хотите. Но предполагая, основываясь на вашем описании, что вы хотите вызвать какой-либо метод после возврата подпрограммы (возможно, передав ему возвращаемое значение), вы МОЖЕТЕ сделать это.

Для этого вам нужно использовать то, что модные люди называют аспектным программированием. Политически (и технически) правильный способ сделать это в Perl - использовать Moose.

Однако вы можете сделать это самостоятельно, в основном заменив оригинальный метод на метод-оболочку.

Точную механику подходов Moose и DIY можно увидеть в первых двух ответах на следующий вопрос SO, поэтому я не буду копировать / вставлять их здесь, надеюсь, вы не возражаете:

Моделирование аспектов статической типизации в языке с утиной типизацией

Если вы любите приключения, вы также можете использовать модуль Scalar::Defer, который предоставляет универсальный механизм для скалярной переменной, чтобы вычислять значение лениво, один раз или при каждом доступе.

Другие вопросы по тегам