harryf has asked for the wisdom of the Perl Monks concerning the following question:

Hi, Have searched around for an answer to this without success - could be I'm searching for the wrong thing but anyway... I'm trying to delay evaluation of a tied scalar to the point where it is actually used in some expression, and not just passed between subroutines. When evaluated, it's actually making a call to a database, and I'm trying to delay this call to the very last moment (e.g. when it's used in a print statement). Easiest to explain in code I guess, which looks something like this...
package lazydbvalue; use strict; use Carp; sub TIESCALAR { # $sth is a prepared DBI statement handle my ($class, $sth) = @_; my $self = {}; $self->{sth} = $sth; bless ($self, $class); return $self; } sub FETCH { my $self = shift; $self->{sth}->execute(); my $row = $self->{sth}->fetchrow_arrayref; $self->{sth}->finish(); return $row->[0]; } sub STORE { my ($self, $sth) = @_; croak('STORE not supported - read only'); } sub DESTROY { my $self = shift; undef $self->{sth}; } package main; use strict; use DBI; # Returns the "lazy" tied scalar sub getNumUsers { my $dbh = shift; my $sth = $dbh->prepare('SELECT COUNT(*) FROM users'); tie my $numUsers, 'lazydbvalue', $sth; return $numUsers; } # Connect to DB etc., assiging connection to $dbh; my $numUsers = getNumUsers($dbh); print "Number of users: $numUsers\n";
The problem is on returning the value from getNumUsers(), the tied FETCH method is already being called. What I want is to delay this until, say, the later print statement, when the value is actually being used, rather than just being passed. I guess the solution is somehow detecting, within the FETCH, whether it's just being passed or whether the value is actually being used. Something like;
sub FETCH { my $self = shift; # Just passing... if ( justPassingTheValue() ) { return $self; } # Value is being used in some expression # Now evaluate the real thing... $self->{sth}->execute(); my $row = $self->{sth}->fetchrow_arrayref; $self->{sth}->finish(); return $row->[0]; }
Something similar wantarray I guess - knowing the calling context. Many thanks.

Replies are listed 'Best First'.
Re: Delay evaluation of tied scalar when returned from sub
by perrin (Chancellor) on Aug 30, 2006 at 13:47 UTC
    Scalar::Defer does what you want. However, my advice is to reconsider and use a normal object instead. It will be much less confusing.
      Scalar::Defer does what you want.

      Perfect! Many thanks. Think "defer" was one keyword I'd missed in all searching.

      However, my advice is to reconsider and use a normal object instead. It will be much less confusing.

      No doubt you're completely right but now I've started... ;) Actually, from glancing at Scalar::Defer and considering I want to do similar for arrays and hashes later, for which CPAN doesn't seem to have anything, that will probably be the end of it. The mission was to be able to replace a set of subroutines which execute queries immediately with "lazy" versions, simply be changing the module being used. Anyway - have learnt something.

      BTW - you're not Perrin as in Perrin Harkins as in Krang are you? If so, we met at OSCOM.4 in Zurich a couple of years ago - was doing that talk on stuff like wrapping output from pod2html in PHP. Been meaning to drop you a "Hello" for a long time. Is this the right email - http://search.cpan.org/~perrin/

        Cheers, Harry! That's my address. Drop me a line.
Re: Delay evaluation of tied scalar when returned from sub
by ysth (Canon) on Aug 30, 2006 at 12:17 UTC
    I think the point you are missing is that tie acts on a container (a variable), but what you are returning is what's in the container, the value, which, as you note, triggers fetch. To put magic on a value, you need overload, not tie.
      OK - thanks - will see how far I can get with overloading.
Re: Delay evaluation of tied scalar when returned from sub
by dave_the_m (Monsignor) on Aug 30, 2006 at 11:04 UTC
    The only practical way to achieve what you want is to pass around a reference to the tied scalar.

    Dave.

      I guess so. That's a shame as it pushes the problem to the caller, and was hoping to keep it transparent - the idea would be to use be able to pass the lazy value to, say, a template engine and only have it evaluated on output, but the template engine (or it's user) is able to be blissfully unaware of what's happening behind the scenes.
Re: Delay evaluation of tied scalar when returned from sub
by cdarke (Prior) on Aug 30, 2006 at 11:04 UTC
    Just a thought. If it really is the print statement when you need the call, then maybe you could use overload:
    use overload  ('""' => 'RealFetch');
    ?
      Interesting. I guess overload could take me some of the way there but it won't just be print; or I don't know how the value may end up being used - might be something like;
      my $percentUsersOnline = ($usersOnline / $numUsers) * 100;
      At which point the value needs evaluating. Haven't played with overload knowingly before but get the feeling it's likely to end up as a messy hack - that the variable gets used in some way I hadn't predicted, and fails to be evaluated. Gets me thinking though - wonder if there's some way by looking the call stack via caller?