Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

hash, a troublemaker?

by Doraemon (Beadle)
on May 12, 2004 at 03:28 UTC ( [id://352638]=perlquestion: print w/replies, xml ) Need Help??

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

I wonder, how to prevent (control) hashes members?
Let say
my %hash; $hash{'name'} = "Doraemon"; $hash{'age'} = "1000"; #... some code # oh! my birthday, i'm getting older $hash{'ege'} = "1001"; # oops, i just add new key
How to avoid this (silly) mistake?
One way i can think of is to define the keys in constants,
$k_name = 'name'; $k_age = 'age'; $hash{$k_ege} = "Do"; # warning, gotcha!
so the compiler can catch the mistake, when use strict. Any more 'efficient' ideas?

Replies are listed 'Best First'.
Re: hash, a troublemaker?
by blokhead (Monsignor) on May 12, 2004 at 03:38 UTC
    You can declare the keys as constants as you suggest, or use the lock_keys function from Hash::Util, which comes standard in recent perls. The latter will raise an exception at runtime if an non-existant key is set.

    BTW, if you do use constants, an array is more efficient unless only a sparse set of keys are ever used at once:

    use constant { K_AGE => 0, K_NAME => 1 }; my @record; $record[K_AGE] = 1001; $record[K_NAME] = 'Doraemon';
    The difficulty in making hashes "strict" is just one of the reasons why many people dislike blessed-hashref object implementations.

    Update: See also: non-autovivifing hash, how to avoid mis-spelling hash keys?.

    blokhead

      You seem to be reinventing fields, and, dare I say it, pseudo-hashes, a now deprecated feature where you could (or still can) use the hash syntax to access fields in an array.

      Borrowing your example, the internal structure for pseudo-hashes is:

      my $record = [ { K_AGE => 1, K_NAME => 2 }, 1001, 'Doraemon' ];
      And you can access it like:
      print "Name: '$record->{K_NAME}' Age: $record->{K_AGE}\n";
      It just works for 5.6.x, still works for 5.8.3 though with a warning, and this feature will be gone from 5.10.0 on. See the docs on fields for more info, including info on what you really should be using instead.
      (The Hash::Util seems to be the best approach, i think)
      For the 'using constant' approach, I never thought about using arrays. Yes, array ... (why i never thought about it, duh!), looks better. Thanx
Re: hash, a troublemaker?
by davido (Cardinal) on May 12, 2004 at 04:01 UTC
    Try this one for size:

    use strict; use warnings; use Hash::Util qw( lock_keys unlock_keys ); my %hash = ( 'name' => "Doraemon", 'age' => "1000" ); lock_keys( %hash ); # oh! my birthday, i'm getting older $hash{'ege'} = "1001"; # oops, just TRIED to add new key.

    ...and the output...

    Attempt to access disallowed key 'ege' in a restricted hash at C:\Perl\scripts\hashtest.pl line 14.

    Enjoy!


    Dave

Re: hash, a troublemaker?
by TilRMan (Friar) on May 12, 2004 at 04:07 UTC

    You might find fields useful, although you'd have to create an object instead of a plain hash, and you'd need a different class for each hash.

    { package FooBar; use fields qw( foo bar ); sub new { fields::new(shift) } } my $ref = new FooBar; $ref->{foo} = 1; # Okay $ref->{quv} = 2; # Kablooie!

    I'd guess that with Symbol one could create a class factory that would return a singleton object with the fields you request.

Re: hash, a troublemaker?
by Abigail-II (Bishop) on May 12, 2004 at 09:30 UTC
    Why are you using a hash in this case? It looks like you are using it where one would use a struct in C. Now, either of two things could be the case: you just have a single hash with this role, or it's one of many. In the first case, you can avoid the hash altogether, and just use variables, $name, $age, etc. If you have more of them, just turn them inside-out. Say you would normally have %person1, %person2, %person3, etc, each with an age, a name, and perhaps more, something like:
    my (%person1, %person2, %person3, ...); $person1 {name} = "..."; $person1 {age} = ...; $person2 {name} = "..."; $person2 {aeg} = ...; # Oopsie. $person3 {name} = "..."; $person3 {age} = ...;
    Instead of hashes for the entities, and attributes as the keys, make hashes for the attributes, using the entities as attributes - just make sure the entities are unique.
    my (%age, %name); my $entitie_count = 0; my $person1 = ++ $entitie_count; my $person2 = ++ $entitie_count; my $person3 = ++ $entitie_count; $name {$person1} = "..."; $age {$person1} = ...; $name {$person2} = "..."; $aeg {$person2} = ...; # Compile time error with use strict. $name {$person3} = "..."; $age {$person3} = ...;

    Abigail

      This is rather ugly, you have a set of lookups for various values without correlation to structures, while the opening example does have problems of hash abuse with misspelling, I'd MUCH rather see field identifiers as constants and an HoH as a datastructure rather than having unaffiliated fields.

      i.e. $records{$person}{$K_AGE}

      That way you keep records grouped. Since Perl doesn't have real structures of any merit, the next logical step is to go to an OO representation and use functions/etc (maybe one of those MethodMaker deals) to prevent accidental typos.

      At minimum, the Hash::Util method provides locking while preserving the logical representation of the data (edit: and clean Data::Dumper dumpability!).

      Keeping fields seperated without context, IMHO, is the worst thing you can do.

        This is just the same technique I use for InsideOut objects, so going to an OO model wouldn't be much of a change for me. ;-). And if you use functions, you can still make typos in the functions when you access the attributes - which is why I developed InsideOut objects in the first place.

        Abigail

        Sorry Abigail-II :)
        I agree with flyingmoose. Related fields should be grouped together, with a name represent these related data. At this point i strongly support the reference mechanism, but somehow, we need to create a reference that have information about the data that it's pointing to.
Re: hash, a troublemaker?
by Doraemon (Beadle) on May 12, 2004 at 06:49 UTC
    I think the reasons why Perl has this feature is somehow related to what Larry Wall said,
    If the burden of decision making is on the programmer, then it's possible for the programmer to make a mess of things. It's possible for Perl programmers to write messy programs. It's also possible for Perl programmers to write extremely clean, concise, and beautiful programs.
    What he never mentioned was, MOST programmers can mess things up so easily
    like me :)
    I think language sometimes shoud be more strict.
      >   I think language sometimes shoud be more strict.
      I very often use a hash to keep information to identifiers I don't know before they jump into existence. The "strictness" you would prefer is more something for fixed data structures.

      pelagic
      I believe features like Hash::Util should be embedded into the language itself, rather than add-on module. If the language is too permissive, it'll not only opened to mistakes, but will increase the size of the code itself, because programmer who like strictness (like me) need to add more codes.
      At this point, i agree with Mr.Stroustroup(C++), that he once suggested (and still standing on it), that this kind of controls should be part of the language itself. The advantages :
      - easy for beginner (secure by default)
      - reduce code
      - clear program
      Anyway, i'm considering this as suggesting, rather that criticize I'm not saying this is your mistakes, Mr Wall :)
Re: hash, a troublemaker?
by mce (Curate) on May 12, 2004 at 12:22 UTC
    Hey,

    Just for the fun of it.

    #!/usr/local/bin/perl package MyTie; use Tie::Hash; @ISA=(Tie::ExtraHash); sub STORE { my $self=shift; my $key=shift; my $value=shift; if ( grep /^$key$/, @{$self->[1]} ) { warn "key $key not valid"; return undef; } return $self->[0]{$key}=$value; } 1; package main; use strict; use Data::Dumper; my $rhash; tie %$rhash, 'MyTie', [qw(not ok)]; $rhash->{b}=2; $rhash->{not}=2; warn Dumper($rhash); 1;
    Of course, the solutions given above are much better, but isn't perl just a lot of fun.......
    Of course, the compiler will not complain on this solution, so it is only a half solution.

    ---------------------------
    Dr. Mark Ceulemans
    Senior Consultant
    BMC, Belgium
      Or, from the CPAN Tie::Hash::FixedKeys. (of course, the Hash::Util::lock_keys solution is better.)
      --
      <http://www.dave.org.uk>

      "The first rule of Perl club is you do not talk about Perl club."
      -- Chip Salzenberg

Re: hash, a troublemaker?
by runrig (Abbot) on May 12, 2004 at 17:44 UTC
Re: hash, a troublemaker?
by Doraemon (Beadle) on May 13, 2004 at 00:54 UTC
    Hi,
    say 'i hate u, Mr Hash' :)
    I've used the Hash::Util. But i got new problem. since i can't use
    # the line below will give warning... if (defined(hash{undefined_key})) { }
    Anyone know how to settle this?
Re: hash, a troublemaker?
by Doraemon (Beadle) on May 13, 2004 at 00:56 UTC
    What i need, how i can determine (by codes) the key is not defined?
      Take a read through perlfunc and look at exists. If you use defined you will be creating the key and not just checking to see if the key exists.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://352638]
Approved by blokhead
Front-paged by broquaint
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others rifling through the Monastery: (4)
As of 2024-04-25 16:35 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found