简体   繁体   中英

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

I'm writing some code where I am using a subroutine as both an lvalue and an rvalue to read and write database values. The problem is, I want it to react differently based on whether it is being used as an lvalue or an rvalue.

I want the subroutine to write to the database when it is used as an lvalue, and read from the database when it is used as an rvalue.

Example:

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

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

The only way I can think of the make this work is to find a way for the subroutine to recognize whether it is being used as an lvalue or an rvalue and react differently for each case.

Is there a way to do this?

Deciding how to behave on whether it was called as an lvalue or not is a bad idea since foo(record_name(...)) would call it as an lvalue.

Instead, you should decide how to behave on whether it is used as an lvalue or not.

You can do that by returning a 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;
}

A little test:

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.

Output:

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

After seeing this, you've surely learned that you should abandon the idea of using an lvalue sub. It's possible to hide all that complexity (such as by using sentinel ), but the complexity remains. The fanciness is not worth all the complexity. Use separate setters and getters or use an accessor whose role is based on the number of parameters passed to it ( $s=acc(); vs acc($s) ) instead.

For this situation you might like to try my Sentinel module.

It provides a function you can use in the accessor, to turn it into a more get/set style approach. Eg you could

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

At this point, the following pairs of lines of code are equivalent

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

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

Of course, if you're not needing to provide the specific get_ and set_ prefixed versions of the methods as well, you could inline them as closures.

See the module docs also for further ideas.

In my opinion, lvalue subroutines in Perl were a dumb idea. Just support ->record_name($subscript, $value) as a setter and ->record_name($subscript) as a getter.

That said, you can use the Want module, like this

use Want;

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

though that will also treat this as an LVALUE:

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

If you want only assignment statements to be treated specially, use want('ASSIGN') instead.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM