develooper Front page | perl.perl5.porters | Postings from November 2001

Re: restricted hashes, and other readonlyness (was "clamp...")

Thread Previous | Thread Next
From:
Nick Ing-Simmons
Date:
November 3, 2001 14:43
Subject:
Re: restricted hashes, and other readonlyness (was "clamp...")
Message ID:
20011103224234.26246.1@bactrian.ni-s.u-net.com
Michael G Schwern <schwern@pobox.com> writes:
>> >#2 are provided by a hash with a fixed keyset, nothing more,
>> >nothing less.  delete(), exists(), keys() all work like regular
>> >hashes.  This is the basic struct.
>> 
>> I happen to want 2a which stops parts of the program deleting random keys
>> in my hash.
>
>You're conflating "deleting a key" with "making a key not allowed".
>If this is allowed:
>
>    $foo{allowed_key} = undef;
>
>then why not this:
>
>    delete $foo{allowed_key};

For the case I have in mind I would be turning on SvREADONLY on 
$foo{allowed_key} so the former would be disallowed. 
I want the non-deleteable form to disallow the latter.

>
>They're just two levels of deletion which we find handy.  It allows
>one to cram FOUR meanings into an individual hash value:
>
>    1) There is a true value here
>    2) There is a false value here
>    3) There is an undefined value here
>    4) There is no value here
>
>#4 is a deleted value.  That last one is important, as you'll see
>below.
>
>Once you delete an allowed key, it does *not* become disallowed:

I know that is what is being proposed - it involves extra flags 
and some moderately convoluted tests in potentially "hot" parts 
of the code. I want to make sure it is worth it.

>
>    delete $foo{allowed_key};
>    $foo{allowed_key} = 'something else';
>
>Preventing deletion is lumped in with locking the values in variant #3.
>
>
>> >#3 is just a fixed keyset plus disallowing delete() and
>> >$locked{$key} = 'foo';
>> 
>> There is also the small matter of turning on SvREADONLY on the values.
>
>What does SvREADONLY do in this case that's different than disallowing
>resetting locked keys?

There are cases where the value does not know it is a member of a hash:

foreach (@locked{'foo','bar','wirble'})
 {
  s/$/-Gottach via an alias to the SV/; 
 } 

some_sub($locked{$key});


>> If we disallow delete then existing keys are the allowed keys.
>
>Not quite.  Just because they're allowed doesn't mean it was ever
>initialized.  You're probably thinking the initialization will be one
>step.  
>
>    struct \%hash, (this => 42, that => 23, whatever => 99);
>    print keys %hash;  # this that whatever
>
>I was thinking of a two step init, more along the lines of Jeff's
>original clamp proposal.  This allows trusted parts of your program to
>change a read-only hash.  It works nicely where the purpose is to
>prevent sloppy code from mucking about with your hash, but to allow
>your own code to do so post-initialization.
>
>    allowed \%hash, qw(this that whatever);
>    $hash{this} = 42;  $hash{that} = 23;
>    mk_read_only \%hash;
>
>    print keys %hash;   # this that
>
>    mk_writeable \%hash;
>    $hash{whatever} = 99;
>    mk_read_only \%hash;
>
>    print keys %hash;   # this that whatever
>
>The two interfaces can live together just fine, just a few HV flag
>flipping XS functions.

I know - the extra flag and the extra tests of it are what bother me.

>> Aside from the (now dead) abuse of keys - the other cause for which I
>> am Devil's advocate is the minimalist one:
>> 
>> One (existing) flag bit SvREADONLY
>>  - error on get of non-existing key
>>  - error on set of non-existing key
>>  - delete is not allowed
>>  - keys is just existing code
>>  - therefore exists on deleted key is non issue
>
>Sorry to sound like a broken record, but these are the functional
>semantics of a 5.005 pseudo-hash.  It didn't work out.  Here's why.
>
>The first two features are the important features of a restricted
>hash.  We're trying to prevent typos and sloppy code, so if you try to
>get/set a disallowed key it's an error.  OK.
>
>Now #3: Disallowing delete.  Why disallow delete?  

To save the code and extra flag to represent deleted slots.

>How does it help
>protect the hash from typos and sloppy code?  As above, if you can do
>this:
>
>    $hash{allowed_key} = undef;

I can disallow that via READONLY bit of the _value_.

>
>why not this
>
>    delete $hash{allowed_key};
>
>we're not trying to protect the values of allowed keys (not with this
>variant) 

I am proposing only one variant - and asking for a case as to why 
the hassle of representing deleted slots it worth the extra.

>> I want folk to explain clearly the draw back of that and why it is
>> not sufficient for appication X. That is which applications need to
>> delete members of enumerated-hash
>
>Ok, back to the Class::DBI example again.  If you look inside
>Class::DBI (it maps RDBMS tables to classes, and rows to objects)
>you'll find a function called _safe_exists() (a copy is below).  
>
>This determined if an object was a regular hash (or if we're using
>5.6, when the semantics of pseudo-hashes became more hash-like) or a
>pseudo-hash.  If it was a pseudo-hash, exists() didn't tell me what I
>wanted to know (at least, it didn't tell me the same thing that a
>regular exists() does).
>
>Why?  Well, Class::DBI was written on top of Class::Accessor.  What
>Class::Accessor does is simply given a list of keys it creates
>accessor methods for them.  

If you are using accessor methods then why do you need any protection
on access to non-allowed members - the methods will not exist!
If you do want protection then presumably it is to catch user going
round the methods - so may as well be draconian - the accessor's
can turn it off!

>So I thought: hmmm, class with a
>predeclared, fixed set of attributes... nice place for a pseudo-hash!
>I think you'll agree, it does sound tailor-made.  Database-mapping
>seems ideally suited for structs.
>
>Wrong.  The problems began when Class::DBI (which isa Class::Accessor)
>decided that it needed to look at $self->{column1} and determine if it
>had been loaded from the database yet.  With a normal hash, we simply
>use exists $self->{column1}.  But with a pseudo-hash, exists() always
>returns true!  So that's no good.
>
>This goes back to what I said above about exists/delete providing a
>fourth type of value on a hash.
>
>What do you do?  Well, you do something like the below, which doesn't
>quite work because it uses defined() for pseudo-hashes which means
>that columns which are NULL are never properly cached.  

Why not fill in NULL slots with a reference to (say) Class:DBI::NULL
object so they _are_ "defined"?

>The other
>option is to keep a second, mirrored struct which simply says if a
>column is cached or not.

Seems resonable to me.
Is a shadow is-cached struct such a big deal (or a bad idea)?

Back to the Subject:

Thank you - that is the best case so far - and thanks for expanding 
it from your skeleton example the other day.

But above is a case for allowing exists to be non fatal not in itself
a case for 'delete' - but I can see how you may get there ...

I am not convinced that making one routine in an external data-base
cache class (which is going to be a little involved anyway) is 
worth slowing down even subset of hash accesses in every perl app even by 
a femto-second.

The quick-fix is 

my $was_odd = SvREADONLY_off(%$self);  # to coin an API
if (exists $self->{'...'}) {
}
SvREADONLY_on(%$self) if $was_odd;


What is going to set up the $self->{} hash with the allowed key set
(i.e. make the allowed() call you use above)?

I assume things go like this :

sub new 
{
 my $class = shift;
 my %self;
 my @allowed = $class->db_columns;
 @self{@allowed} = (undef) x @allowed;
 Restrict(%self);
 delete @self{@allowed}; # none-cached yet
 return bless \%self,$class;
}

One could though do this:

sub new 
{
 my $class = shift;
 my %self;
 my @allowed = $class->db_columns;
 # While we know what keys we can have ...
 foreach my $possible (@allowed)
  {
   tie $class.'::Loadme',$self{$possible},\%self,"$possible";
  } 
 NISRestrict(%self);
 return bless \%self,$class;
}

Now all the values exist and don't need delete-ing.
The ones that are not there yet magically load themselves when 
accessed - and once loaded (an IO op anyway to tie overhead is 
less important) can untie themselves.

-- 
Nick Ing-Simmons
http://www.ni-s.u-net.com/


Thread Previous | Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About