开发者

returning a lazily-computed scalar, in perl

开发者 https://www.devze.com 2023-01-09 13:55 出处:网络
I\'m trying to add some functionality to our code base by using tied scalars. We have a function which is specified to return scalars. I thought I could add some features to the system by tie-ing the

I'm trying to add some functionality to our code base by using tied scalars.

We have a function which is specified to return scalars. I thought I could add some features to the system by tie-ing these scalars before returning them, but it looks like the FETCH method is called just before the return, which results in an untied scalar being returned.

Is there any way around this?

I really want to keep the subroutine's interface (returning scalars) intact if it's at all possible.

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

Desired output:

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

I can get the desired output by retu开发者_C百科rning a reference, and dereferencing on each access, but that ruins our established interface, and makes it more confusing for our users.

--Buck


As DVK said, tie applies to containers, so isn't useful for returned values.

For that, you use overloading. An example (not all the possible overloaded operations are supplied; see http://perldoc.perl.org/overload.html#Minimal-set-of-overloaded-operations):

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


As mentioned in other answers, tie applies to containers, and not to values, so there is no way to assign a tied variable to another variable and retain the tied properties.

Since assignment is out, you need to pass the container into the GetThing routine. You can do this by reference as follows:

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

which produces the correct output.

However, if you want to retain the assignment, you will need to use overloading, which applies to values (actually to objects, but they themselves are values). Without more detail on your intended purpose it is hard to give a complete answer, but this will meet your stated requirements:

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

Both ties and overloads can get complicated, so read through all of the documentation if anything is not clear.


First, the exact method of doing what you are proposing seems technically impossible:

  1. Tied variables have the tie attached to the variable itself, not to its value.

  2. In Perl, subroutine's return values are returned by value, meaning you take the value passed to return, access it (in you case, accessing the tied variable and calling FETCH in the process) - and then copy that value! Which means that what the caller gets is a scalar VALUE, not a scalar variable (tied or untied).

Your confusion, in short, seems to stem from mixing together variables (locations in program's symbol table) and values stored in those variables.


Second, you were somewhat unclear as to what exactly you are trying to achieve, so it's hard to propose how to achieve what you want. But assuming, based on your description, that you wanted to call some method upon subroutine's return (possibly passing it the return value), you CAN do that.

To do so, you need to employ what fancy people call aspect programming. The politically (and technically) correct way of doing it in Perl is by using Moose.

However, you can DIY it, by basically replacing the original method with a wrapper method.

The exact mechanics of both Moose and DIY approaches can be seen in the first two answers to the following SO question, so I won't copy/paste them here, hope you don't mind:

Simulating aspects of static-typing in a duck-typed language


If you're feeling adventurous, you could also use the Scalar::Defer module which provides a general-purpose mechanism for a scalar variable to compute a value lazily, either once or on each access.

0

精彩评论

暂无评论...
验证码 换一张
取 消