One rap against the implementation of OO Perl by lvalue closures is that no hook is available for checking the validity of input. Code before the last line of an lvalue sub is ignored in lvalue assignment.
It struck me that all that is needed is some user sub that can subvert assignment. I realized that if the closure were on a tied variable, the STORE method could do that. Time for an experiment...
For this demo, I define a tied scalar package, Constrained, which takes a coderef in its constructor. The sub takes one argument and returns true if the argument is valid, false otherwise. I'll fill in the details in the listing.
After preliminaries,
I define the Constrained namespace, which is to implement a tied scalar checked against a user-supplied validation routine before storing a value. Errors will throw POSIX EINVAL.#!/usr/bin/perl use warnings; use strict;
The _invalidate sub is called with an Errno as argument. This technique is likely to be used with numeric constraints, where EDOM and ERANGE might be more natural. I don't make any use of that here.package Constrained; use Errno qw/EINVAL/;
As an exception to the constraint regime, I permit a newly tied variable to be undef if no initializer is supplied. That will make it necessary to rule that definedness is required in the FETCH method below.sub _invalidate { $! = shift; die sprintf('Constraint violation: %s by %s::%s in %s line %s.', $!, map { qq($_) } (caller 1)[0,3,1,2] ), "\n"; }
STORE() is called in assignment and modification, so we highjack it to throw if fed bad data.sub TIESCALAR { my $class = shift; if (defined $_[1]) { $_[0]($_[1]) or _invalidate EINVAL; } bless { code => $_[0], val => $_[1]}, $class; }
FETCH() is called when anything reads the value. We require definedness so that we get tested data back.sub STORE { my ($self, $val) = @_; $self->{code}($val) or _invalidate EINVAL; $self->{val} = $val; }
To try the idea, let's produce a tied scalar which can only be assigned strings of the form 'cvvcc' (English vowels and consonants). We get back into main::, cook up some shorthand for vowel and consonant character classes, and fire up the closure on the tied variable.sub FETCH { defined $_[0]{val} or _invalidate EINVAL; $_[0]->{val}; } sub DESTROY { # nothing to do } 1;
Finally, we run a series of tests to show what's working.package main; my ($v, $c) = (qr/[aeiouy]/i, qr/[bcdfghjklmnpqrstvwxyz]/i); { tie my $foo, 'Constrained', sub {shift=~/^$c$v$v$c$c$/}; sub foo () :lvalue { $foo } }
print q(Testing FETCH error for undefined value,), $/; defined( eval { print 'foo is ', foo, $/ }) or print $@, $/; print q(Testing STORE error for invalid value, 'quasi',), $/; print defined( eval { foo = 'quasi'}) ? ('foo is ', foo, $/, $/) : ($@, $/); print q(Testing STORE error valid value, 'quash',), $/; print defined( eval { foo = 'quash'}) ? ('foo is ', foo, $/, $/) : ($@, $/); print q(Testing modification, 'foo =~ s/h/e/',), $/; print defined( eval { foo =~ s/h/e/}) ? ('foo is ', foo, ' - ERROR', $/, $/) : ($@, $/); print q(Testing TIESCALAR error invalid value, 'suite',), $/; print defined( eval { tie my $bar, 'Constrained', sub {shift=~/^$c$v$v$c$c$/}, 'suite'; sub bar () :lvalue { $bar } }) ? ('bar is ', bar, $/, $/) : ($@, $/); print q(Testing TIESCALAR error valid value, 'suits',), $/; print defined( eval { tie my $baz, 'Constrained', sub {shift=~/^$c$v$v$c$c$/}, 'suits'; sub baz () :lvalue { $baz } }) ? ('baz is ', baz, $/, $/) : ($@, $/);
__END__ Testing FETCH error for undefined value, Constraint violation: Invalid argument by main::Constrained::FETCH in +lvval.pl line 50. Testing STORE error for invalid value, 'quasi', Constraint violation: Invalid argument by main::Constrained::STORE in +lvval.pl line 53. Testing STORE error valid value, 'quash', foo is quash Testing modification, 'foo =~ s/h/e/', Constraint violation: Invalid argument by main::Constrained::STORE in +lvval.pl line 63. Testing TIESCALAR error invalid value, 'suite', Constraint violation: Invalid argument by main::Constrained::TIESCALAR + in lvval.pl line 70. Testing TIESCALAR error valid value, 'suits', baz is suits
Destructive comments will be treasured.
After Compline,
Zaxo
Back to
Meditations