Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

constraining the keys of a HashRef in Moose

by tomgracey (Beadle)
on Aug 29, 2013 at 14:48 UTC ( #1051451=perlquestion: print w/ replies, xml ) Need Help??
tomgracey has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks

What is the easiest way to constrain the keys of a HashRef in Moose?

I have been looking through the various type constraint modules in Moose (for the first time!) and am feeling a little overwhelmed.

I initially thought of something on the lines of

#!/usr/bin/perl package Test::HashK; use Moose; use Moose::Util::TypeConstraints; enum 'colors', [qw(red green blue)]; has scores => ( is => 'rw', isa => 'HashRef[colors]', default => sub{{ +}} ); # I want the allowed *keys* to be colors 1;
but then if I run the following script:
#!/usr/bin/perl use strict; use Test::HashK; my $hk=Test::HashK->new; $hk->scores->{'red'} = 53; $hk->scores->{'black'} = 100; $hk->scores->{1} = 'blue'; $hk->scores->{2} = 'dinosaur';

it runs through to completion without any problem, thus not appearing to contrain either the keys or the values

Obviously I have no idea what I am doing! Any thoughts?

Comment on constraining the keys of a HashRef in Moose
Select or Download Code
Re: constraining the keys of a HashRef in Moose
by kennethk (Monsignor) on Aug 29, 2013 at 15:30 UTC
    IMHO you are thinking about this problem incorrectly. If you are using an object framework, then you should probably be using an object if you need a complex data behavior. This means scores should be a subtype with fields that are the allowed values, perhaps like:
    #!/usr/bin/perl package Test::HashK; use Moose; use Moose::Util::TypeConstraints; our @_allowed_colors = qw(red green blue); enum 'colors', \@_allowed_colors; has scores => ( is => 'rw', isa => 'Test::HashK::Score', default => su +b{Test::HashK::Score->new()} ); # I want the allowed *keys* to be col +ors package Test::HashK::Score; use Moose; for my $color (@Test::HashK::_allowed_colors) { has $color => is => 'rw', isa => 'Num', ; } 1;

    If you do want to keep it as an explicit hash, rather than a blessed object, you could use Hash::Util's lock_keys or lock_ref_keys:

    my @colors = qw(red green blue); enum 'colors', \@colors; has scores => ( is => 'rw', isa => 'HashRef', default => sub { use Hash::Util 'lock_keys'; my %hash; lock_keys %hash, @colors; return \%hash; } );
    Of course, the user could easily bypass this by inserting their own value. As with most things in Perl, it gives you all the rope you want, and if you want to work outside provided frameworks, that's your issue.

    #11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.

Re: constraining the keys of a HashRef in Moose
by tobyink (Abbot) on Aug 29, 2013 at 17:49 UTC
    package Test::HashK; use Moose; use Types::Standard qw( Dict Optional Int ); use Types::ReadOnly qw( Locked ); my $colour_hash = Locked[ Dict[ red => Optional[Int], blue => Optional[Int], green => Optional[Int], ], ]; has scores => ( is => 'rw', isa => $colour_hash, default => sub{ {} }, coerce => 1, ); __PACKAGE__->meta->make_immutable; package main; my $thk = Test::HashK->new(scores => { green => 0 }); $thk->scores->{red} = 2000; $thk->scores->{bloo} = 'tobyink'; # dies

    PS: this is my 2000th post to PerlMonks!

    use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name
      PS: this is my 2000th post to PerlMonks!

      Congratulations. We are lucky to count you among us, IMO, YMMV, IIRC, TIMTOWTDI, IANAL, etc. Took me 10 years. Not jealous though. Not a bit. You deserve the success. And sports cars and fancy desserts and nice clothes and emu farms and everything else I ever wanted that you’ve beaten me at^H^H to… I'm just going to go in the other room and chop onions for a bit.

        Thanks kennethk and tobyink - really helpful.

        kennethk: I did think of making the hash into an object as per your first suggestion, but this would mean creating a large number of new packages (I think! The real situation is more complex than the simplified example). Using Hash::Utils is a very good idea though - this does what I need perfectly.

        tobyink: nice - this is the kind of Moosey thing I had in mind in the first place. There seem a great many packages which *might* be relevant (to me starting out at least) so its very helpful to know which someone experienced would pick. Congratulations on your 2000th post!

      Hi Tobyink,

      thanks for your help earlier and for recommending the very useful Types::Standard and Types::ReadOnly modules - which I now believe you wrote (?) so i hope you dont mind me bugging you with a related question!

      Suppose I have a module with the code you suggested:

      my $colour_hash = Locked[ Dict[ red => Optional[Int], blue => Optional[Int], green => Optional[Int], ], ]; has scores => ( is => 'rw', isa => $colour_hash, default => sub{ {} }, coerce => 1, );

      but now I have another package which inherits from this one. Is there any simple way to extend the list of allowed keys in the inherited class?

        I'd split out type constraint definitions into a separate type library module. Here's a quick example:

        use strict; use warnings; # thanks.pm is a shim for declaring multiple packages in the same file +. # It's on CPAN if you want it. Otherwise, just split them into separat +e # files. no thanks qw( MyApp::Types MyApp::Printer MyApp::Printer::ThreeColour MyApp::Printer::FourColour ); BEGIN { package MyApp::Types; use Type::Library -base, -declare => qw( ThreeColour FourColour ); use Type::Utils; use Types::Standard -types; use Types::ReadOnly -types; declare ThreeColour, as Locked[ Dict[ cyan => Optional[Num], magenta => Optional[Num], yellow => Optional[Num], ] ], coercion => 1; # black is referred to as "key" in CYMK printing. presumably becau +se # using a name that abbreviated to "B" would be confused with "blu +e". declare FourColour, as Locked[ Dict[ @{ ThreeColour->parent->wrapped->parameters }, # voodoo? key => Optional[Num], ] ], coercion => 1; }; BEGIN { package MyApp::Printer; use Moose::Role; # Let's assume the printer is only capable of printing one pixel. has pixel => (is => 'ro'); sub dump { require Data::Dumper; Data::Dumper::Dumper(@_); } }; BEGIN { package MyApp::Printer::ThreeColour; use MyApp::Types -types; use Moose; with qw(MyApp::Printer); has '+pixel' => (isa => ThreeColour, coerce => 1); }; BEGIN { package MyApp::Printer::FourColour; use MyApp::Types -types; use Moose; with qw(MyApp::Printer); has '+pixel' => (isa => FourColour, coerce => 1); }; my $printer = 'MyApp::Printer::FourColour'->new( pixel => { cyan => 9.4, magenta => 7, yellow => 4 }, ); print($printer->dump); $printer->pixel->{key} = 9; # ok print($printer->dump); $printer->pixel->{black} = 6; # dies
        use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1051451]
Approved by hdb
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others examining the Monastery: (10)
As of 2014-07-25 19:59 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My favorite superfluous repetitious redundant duplicative phrase is:









    Results (175 votes), past polls