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

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

Thread Previous | Thread Next
Michael G Schwern
November 2, 2001 11:44
Re: restricted hashes, and other readonlyness (was "clamp...")
Message ID:
On Fri, Nov 02, 2001 at 08:33:33AM +0000, Nick Ing-Simmons wrote:
> Michael G Schwern <> writes:
> >There seem to be many "wants" here for read-only hashes:
> >
> >    1) protect against key typos
> >    2) protect against other parts of the program putting random crap
> >       in your hash (usually objects and subclasses)
> >    3) provide a constant hash (ie. fixed keys, fixed values)
> >
> >#1 is the "any read access to a key which doesn't exist is an error".
> And write access ...

Write, right.

> >#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};

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

Once you delete an allowed key, it does *not* become disallowed:

    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?

> >On Thu, Nov 01, 2001 at 07:33:49AM -0800, Jeffrey Friedl wrote:
> >> This natural, drop-in approach feels like The Perl Way to me, which is why
> >> I feel that keys() should maintain the same returns-only-existing-keys
> >> semantics.
> 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

    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.

If you use the first, keys() == allowed keys by a happy coincidence,
no extra code needed.  If you use the second, keys() == initialized
keys, as you'd expect.

So it works both ways!

> >Finally, the argument that we can shave a few bytes of memory by
> >adding a few inconsistencies into the implementation seems a throwback
> >to pseudo-hashes.
> 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?  How does it help
protect the hash from typos and sloppy code?  As above, if you can do

    $hash{allowed_key} = undef;

why not this

    delete $hash{allowed_key};

we're not trying to protect the values of allowed keys (not with this
variant) just that you don't assign to disallowed keys.  So #3 isn't a
necessary feature of a restricted hash.  It causes an unnecessary
difference between regular hashes and restricted hashes.  It was these
little unnecessary differences that made 5.005 pseudo-hashes hard to

And delete() != disallow.  It's perfectly valid to reassign to a
disallowed key.  The implementation will probably be very similar to
how av_delete and av_exists work (ie. PL_sv_undef).

The last two are just corollaries of #3.

> 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.  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.  The other
option is to keep a second, mirrored struct which simply says if a
column is cached or not.

So that's my real-world, battle-tested case of where a hash-as-struct
fell on it's face.  This relatively simple problem disallows a whole
class of otherwise perfectly suited Perl modules (ie. object
persistence/RDBM mapping) from effectively using restricted hashes.

I'll try to dig up more 5.005 pseudo-hash battle stories.

# In perl < 5.6 exists() doesn't quite work the same on pseudohashes
# as on regular hashes.  In order to protect ourselves we define our own
# exists function.
use constant PERL_VERSION => $];
sub _safe_exists {
    my($hash, $key) = @_;

    # Because PERL_VERSION is constant this logic will
    # be optimized away in Perl >= 5.6 and reduce to a simple
    # statement.
    # Either its 5.6 or its a hash.  Either way exists() is
    # safe.
    if( PERL_VERSION >= 5.006 ) {
        return exists $hash->{$key};
    else {
        # We can't use ref() since that won't work on objects.
        if( UNIVERSAL::isa($hash, 'HASH') ) {     # hash
            return exists $hash->{$key};
        # Older than 5.6 and its a pseudohash.  exists() will always return
        # true, so we use defined() instead as a cheap hack.
        else {
            return defined $hash->{$key};


Michael G. Schwern   <>
Perl6 Quality Assurance     <>	     Kwalitee Is Job One
....and I pull out the Magnum from under the desk where I keep it in case
someone laughs at a joke that's so dry it's got a built in
water-fountain, and blow the lot of them away as a community Service.
	-- BOFH

Thread Previous | Thread Next Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at | Group listing | About