Concerning code factorization, efficiency and tied variables:
In the wake of Are lvalue methods (as properties) a good idea?, I've revisited an old node of mine, To Validate Data In Lvalue Subs. One thing that I realize now is that I didn't make clear how independent the Constrained class is from the notions of closures and lvalue subs. Constrained can be applied to any scalar variable, including references to anything.
What good is that? A tied interface is slower than simple assignment for a reason; it's doing something. The 40% (or whatever) extra overhead for assigning or fetching through the tied interface is trivial if you have work to do inside it. What work? Whatever is ubiquitous enough to factor this way! I think that that is more than "syntactic sugar", and it's sweeter, too.
For reference, here is a slightly updated and renamed version of Constrained, package Tie::Constrained;
use Errno qw/EINVAL EDOM ERANGE/; sub TIESCALAR { my $class = shift; my $self = { test => defined $_[0]? $_[0]: \&validate }; $self->{test}($_[1]) or invalid(EINVAL) if defined $_[1]; $self->{val} = $_[1]; bless $self, $class; } sub STORE { my ($self, $try) = @_; $self->{test}($try) or invalid(EINVAL); $self->{val} = $try; } sub FETCH { $_[0]->{val}; } sub DESTROY {} sub validate { 1 } sub invalid { $! = shift; die sprintf("Constraint violation: %s by %s::%s in %s line %s.\n", $!, map { qq($_) } (caller 1)[0,3,1,2] ); } 1; __DATA__
That's much like the older code, but I've removed the stringency from FETCH() and added a dummy default validator. Those changes were to make subclassing easier. A subclass would typically override invalid() to get different error handling or validate() to have a common class-wide default test.Usage: use Tie::Constrained; tie my $var, Tie::Constrained => \&mytest, $initval; Both arguments are optional, but the default validator function always says yes. mytest() should be designed to return true for valid data and false for data to reject.
Data validation is the job the Tie::Constrained class does. That's an example of something you may want to do many times, the same way each time, every time a mutator is applied to your variable.
Off the top of my head, I can think of three basic ways to code those tests.
- Paste in a call to a validator function after each mutation. At least, just before each use where the validity matters.
- Bless the variable into a class which overloads mutators to validate.
- Tie to Tie::Constrained
The first and most obvious one is dismal in practice. The mutation is done to your variable before you get to validate. You can't apply it to third-party code. Paste errors may gum you up. Maintainance is a nightmare. The limited checking of 1b) does nothing to inform you where the bad data crept in. The code has flashing neon signs saying "Factor me!"
The second is better, but it has problems of its own. The proliferation of classes confuses development. Overriding core functions and overloading core operators confuses everybody. The class packages represent a lot of perhaps tricky code to write. Factorization is pretty good, but nothing like . . .
Three. Once a variable is tied to its very own automatic validity check, every mutator will be checked before the variable is modified. That is true of old code and new, third-party code, perl modules, all without the code needing to know anything about it. No infrastructure at all. No special coding beyond the tie call.
That is code factoring with a vengence. I also consider it a particularly sparse and clean kind of OO code, where the object is the aggregate of variable, test and exception.
I'm considering doing a little more tuning and much pod writing to prepare a distribution for CPAN. I'll welcome your comments.
After Compline,
Zaxo
|
---|
Replies are listed 'Best First'. | |
---|---|
Re: Tie Me Up, Tie Me Down
by diotalevi (Canon) on Jan 16, 2005 at 16:55 UTC | |
by Zaxo (Archbishop) on Jan 16, 2005 at 20:27 UTC |