views:

116

answers:

4

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 returning a reference, and dereferencing on each access, but that ruins our established interface, and makes it more confusing for our users.

--Buck

+2  A: 

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:

http://stackoverflow.com/questions/2933438/simulating-aspects-of-static-typing-in-a-duck-typed-language

DVK
**Caveat**: I never used tied scalars, so the fiest part of the answer is merely a result of my analyzing documentation, and therefore could be way off, like anything based on theory... i'll gladly delete it when/if someone with more clue comes along with a better one.
DVK
the line between variables and values gets blurred when the returning subroutine is declared as an `lvalue` (since `FETCH` and `STORE` both work in that case). you can also take a reference to the returned alias, which retains the tied action when it is dereferenced.
Eric Strom
@Eric Strom: if you are going to that trouble, might as well make the routine return a reference (or be passed one!) in the first place
ysth
@ysth => I'm not sure I follow what you mean. Do you mean return a reference to the tied scalar? That would force the user to dereference it each time, which seems to defeat the elegance of the tied interface. My point was more that you "could" take the reference, proving that the return "value" is the tied variable. Take a look at the source for my module `Lvalue` ( http://search.cpan.org/perldoc?Lvalue ) for an example of how tied scalars and lvalue subroutines can come together to extend perl's syntax (without the inherent encapsulation violations of traditional lvalue subroutines)
Eric Strom
@DVK: Thanks, I read your linked posts, but was unable to figure out how it applies here... Can you be more explicit? I'd really like to find a perl-builtin solution (DIY, I guess), rather than a new module to install.
bukzor
@Eric Strom: If you think lvalues are a solution, please create an answer :)
bukzor
lvalues subs are gross, just my opinion. Yes, I did propose returning a reference to the tied scalar as an inelegant solution.
ysth
+3  A: 

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";
}
ysth
@ysth - could you please elaborate on how overloading would apply in this situation? Thx!
DVK
@ysth - yes please elaborate. I thought tie() was the correct way to overload FETCH on a scalar.
bukzor
@buzkor: on a scalar variable, not a scalar value.
ysth
@DVK: added an example.
ysth
+2  A: 

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.

Eric Strom
@Eric Strom: that looks like it might do the trick. The purpose is to compute my strings on-demand, in a lazy fashion, and to also do some expensive operations on values as-needed. Our users may create many values, but end up only using a few. The users are non-expert to say the least, so if we can make it look like a plan string, thats best. I'll try it out and get back to you in a minute.
bukzor
@Eric Strom: works like a charm! I've tweaked the title to better reflect my problem. I'd remove the first half of your answer.
bukzor
@bukzor => you can also take a look at my module `List::Gen` on CPAN which contains a fairly robust implementation of lazy lista
Eric Strom
A: 

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.

John Siracusa
That seems better than writing my own. Have you used it? It's unclear from the doc: Does $x/2 return another deferred value, or a plain value?
bukzor
It will return a plain value.
John Siracusa