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

I decided to do some experimenting with tied variables, and I've found myself with two questions that I hope the collective wisdom of the monks may be able to help with.

First, the scenario. I decided to create an "Enforcer" class that lets me attach semi-permanent assertions to scalar variables. If any assignment violates the assertion, the program croaks. Here's a simple example:

my $positive_number; tie $positive_number, "Enforcer", sub {$_[0] > 0}; $positive_number = 2; # ok $positive_number = 6.4; # ok $positive_number = -1; # croak!
So, having written a simple class to implement this, my first question is whether I'm reinventing the wheel. I didn't see anything that does precisely this on CPAN (though I suppose Tie::Watch could be co-opted for this purpose), but I can hardly claim comprehensive knowledge of everything that's on CPAN! (I'll probably go on and implement Enforced arrays and hashes anyway just for the learning experience, but I'd really like to know if somebody else has already been down this path.)

My second question is whether the TIESCALAR constructor can actually have access to the value of the variable that's being tied. In other words:

my $x = 2; tie $x, "Enforcer", sub {...} # can Enforcer::TIESCALAR know that $x is currently 2??

I didn't find any mention of this in perl manpages, The Camel, or Damian's OOP book -- nor did this thread, which also raised the question, seem to lead to a definitive yes-or-no answer.

I know I could get access to the value by stipulating that the tie be called like this:
tie $x, "Enforcer", $x, sub {...}
but that seems inelegant, to say the least.

Anyway, I'd appreciate any thoughts/comments/pointers on this topic... thanks!

Replies are listed 'Best First'.
Re: two-part question about tied variables
by japhy (Canon) on Jan 13, 2002 at 07:30 UTC
    Well, you could use my (as yet unpublished) Loop::Watch module, which allows you to write code like:
    use Loop::Watch; my $x = 2; ensure { $x > 0 } watching($x), doing { # all your code centered around $x here };
    Don't worry -- that's not a loop. The doing() construct means "do this once", as opposed to looping().

    There are probably some assertion modules out there, and mine is one such module.

    As for your question about passing the value, the answer is to not use tie(). "Huh?" Well, don't have your interface use tie(). Have it use some auxiliary function that calls tie() with the appropriate duplicated value. Here's how I would write the application.

    package Tie::Scalar::Watch; use Carp; use strict; # rich man's Exporter ;) # this just puts 'watch()' in the caller's namespace sub import { my $pkg = caller; no strict 'refs'; *{ $pkg . "::watch" } = \&watch; } sub TIESCALAR { my ($class, $val, $rule) = @_; my $self = bless [ undef, $rule ], $class; $self->STORE($val); return $self; } sub FETCH { $_[0][0] } sub STORE { my ($self, $val) = @_; return $self->[0] = $val if $self->[1]->($val); croak "Value $val is out of bounds"; } sub watch (&$) { tie $_[1], __PACKAGE__, $_[1], $_[0]; } 1;
    Above is the module responsible for keeping track of the scalar. Now is the code that uses it.
    use Tie::Scalar::Watch; # imports 'watch' my $x = 10; watch { $_[0] > 5 } $x; # $x is now transparently tied! # and you thought tying was transparent already! while (1) { print "$x\n"; $x--; }
    That code doesn't run forever -- it prints 10, 9, 8, 7, and 6, and then dies with Value 5 is out of bounds at ... line 11.

    If you want to know how to write the code such that you can say watch { $foo > 5 } $foo instead of using the "ugly" $_[0] notation, here's how:

    sub STORE { my ($self, $val) = @_; $self->[0] = $val; $self->[1]->() ? return $val : croak "Value $val is out of bounds"; }
    Sneaky, no? That's all for now. I hope this helps you out.

    _____________________________________________________
    Jeff[japhy]Pinyan: Perl, regex, and perl hacker.
    s++=END;++y(;-P)}y js++=;shajsj<++y(p-q)}?print:??;

      Japhy, your code does just about everything I was hoping for and then some... it's a really slick solution. Thanks!
      Shouldn't you have a use 5.6.0; at the top of your Tie::Scalar::Watch? watch(&$) won't work as advertised in 5.0.x, will it?

      ------
      We are the carpenters and bricklayers of the Information Age.

      Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

        It works in 5.005, at least. And in this day and age, I'd hope people are at least using 5.005.

        _____________________________________________________
        Jeff[japhy]Pinyan: Perl, regex, and perl hacker.
        s++=END;++y(;-P)}y js++=;shajsj<++y(p-q)}?print:??;

Re: two-part question about tied variables
by IlyaM (Parson) on Jan 13, 2002 at 07:14 UTC
    Anwser to second question. AFAIK when you apply tie magic to any variable it loses its original value. And TIESCALAR doesn't know old value of variable.

    --
    Ilya Martynov (http://martynov.org/)

Re: two-part question about tied variables
by robin (Chaplain) on Jan 13, 2002 at 22:42 UTC
    In answer to your second question, I'm fairly sure that there's no standard way to get the value of the tied variable from the TIESCALAR routine. Artur Bergman has been bugging me for months to write a routine to do that, but I haven't got round to it yet. One day I shall...
Re: two-part question about tied variables
by Stegalex (Chaplain) on Jan 13, 2002 at 17:56 UTC
    Just by way of confirmation, you know about

    use constant

    right?