检查子程序是否被用作 Perl 中的左值或右值

Check if a subroutine is being used as an lvalue or an rvalue in Perl

我正在编写一些代码,其中我使用子例程作为左值和右值来读取和写入数据库值。问题是,我希望它根据它是用作左值还是右值而做出不同的反应。

我希望子程序在用作左值时写入数据库,用作右值时从数据库中读取。

示例:

# Write some data
$database->record_name($subscript) = $value;

# Read some data
my $value = $database->record_name($subscript);

我能想到的唯一方法是为子例程找到一种方法来识别它是被用作左值还是右值,并对每种情况做出不同的反应。

有办法吗?

在我看来,Perl 中的左值子例程是一个愚蠢的想法。只支持 ->record_name($subscript, $value) 作为 setter 和 ->record_name($subscript) 作为 getter。

也就是说,您可以像这样使用 Want 模块

use Want;

sub record_name:lvalue {
    if ( want('LVALUE') ) {
        ...
    }
    else {
        ...
    }
}

虽然这也会将其视为 LVALUE:

foo( $database->record_name($subscript) );

如果您只想对赋值语句进行特殊处理,请改用 want('ASSIGN')

对于这种情况,您可能想试试我的 Sentinel 模块。

它提供了一个可以在访问器中使用的函数,将其变成更 get/set 风格的方法。例如。你可以

use Sentinel qw( sentinel );

sub get_record_name { ... }
sub set_record_name { ... }

sub record_name
{
   sentinel get => \&get_record_name,
            set => \&set_record_name,
            obj => shift;
}

至此,下面两行代码是等价的

$name = $record->record_name;
$name = $record->get_record_name;

$record->record_name = $new_name;
$record->set_record_name( $new_name );

当然,如果您不需要提供特定的 get_set_ 前缀版本的方法,您可以将它们内联为闭包。

另请参阅模块文档以获取更多想法。

根据是否调用作为左值来决定如何表现是一个坏主意,因为foo(record_name(...))会将其作为左值调用。

相反,您应该根据它是否用作左值来决定如何表现。

您可以通过返回 magical value 来做到这一点。

use Variable::Magic qw( cast wizard );

my $wiz = wizard(
   data => sub { shift; \@_ },
   get => sub { my ($ref, $args) = @_; $$ref = get_record_name(@$args); },
   set => sub { my ($ref, $args) = @_; set_record_name(@$args, $$ref); },
);

sub record_name :lvalue {
   cast(my $rv, $wiz, @_);
   return $rv;
}

小测试:

use Data::Dumper;

sub get_record_name { print("get: @_\n"); return "val"; }
sub set_record_name { print("set: @_\n"); }

my $x = record_name("abc", "def");        # Called as rvalue

record_name("abc", "def") = "xyz";        # Called as lvalue. Used as lvalue.

my $y_ref = \record_name("abc", "def");   # Called as lvalue.
my $y = $$y_ref;                          #   Used as rvalue.
$$y_ref = "xyz";                          #   Used as lvalue.

输出:

get: abc def
set: abc def xyz
get: abc def
set: abc def xyz

看完之后,您肯定知道应该放弃使用左值子的想法。可以隐藏所有复杂性(例如通过使用 sentinel),但复杂性仍然存在。幻想不值得所有的复杂性。使用单独的 setter 和 getter 或使用其角色基于传递给它的参数数量的访问器($s=acc(); vs acc($s))。