Perl: do and eval result in different answers? - perl

I have a file with the following statements in it:
{
%{do '/tmp/personcontact.pl'},
%{do '/tmp/address.pl'}
}
Now, the temp files are as follows:
Personcontact.pl :
{
'firstname' => {
'__type' => 'String'
},
'lastname' => {
'__type' => 'String'
}
}
Address.pl:
{
'address' => {
'street' => {
'__type' => 'String'
},
'unit' => {
'__type' => 'String',
},
'suburb' => {
'__type' => 'String'
},
'__type' => 'HASH'
}
}
Now, when I do :
open(SCHEMAFILE, "<", $schema) or return undef;
my $schemafile;
while(my $line = <SCHEMAFILE>) { $schemafile .= $line;}
my $tempref = eval $schemafile;
print Dumper $tempref;
The result is $VAR1 = '1/8'
And when I do :
print Dumper do "/tmp/schemawithinschema.pl";
The result is
$VAR1 = 'firstname';
$VAR2 = {
'__type' => 'String'
};
$VAR3 = 'address';
$VAR4 = {
'suburb' => {
'__type' => 'String'
},
'unit' => {
'__type' => 'String'
},
'street' => {
'__type' => 'String'
},
'__type' => 'ARRAY'
};
$VAR5 = 'lastname';
$VAR6 = {
'__type' => 'String'
};
What's wrong here? Thanks!

Alright, to keep this from perpetuating forever, here is a module-based solution for you:
Foo.pm:
package Foo;
use strict;
use warnings;
BEGIN {
require Exporter;
our #ISA = qw( Exporter );
our #EXPORT_OK = qw( get_person get_address get_all );
our $VERSION = '0.01';
}
my %person = (
firstname => {
__type => 'String',
},
lastname => {
__type => 'String',
},
);
my %address = (
address => {
street => {
__type => 'String',
},
unit => {
__type => 'String',
},
suburb => {
__type => 'String',
},
__type => 'HASH',
},
);
sub get_person
{
return \%person;
}
sub get_address
{
return \%address;
}
sub get_all
{
return( { %person, %address } );
}
1;
__END__
bar.pl:
#!/usr/bin/perl
use Data::Dumper;
use strict;
use warnings;
use lib '.';
use Foo qw( get_person get_address get_all );
my $junk = get_all();
print Dumper $junk;
But really, for the sake of your maintenance programmer (often yourself in 6 months), use JSON or YAML (or the faster YAML::XS), so that the data can be maintained as a simple-ish text file, instead of a series of nested data-disguised-as-code references.
To quote Perl Best Practices (not sure if it was Damian originally):
Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
EDIT: For completeness, here is the equivalent solution using YAML (from CPAN):
data.yml:
---
firstname:
__type: String
lastname:
__type: String
address:
__type: HASH
street:
__type: String
suburb:
__type: String
unit:
__type: String
baz.pl:
#!/usr/bin/perl
use YAML qw( Load Dump LoadFile DumpFile );
use Data::Dumper;
use strict;
use warnings;
my $data = LoadFile( 'data.yml' );
print Dumper $data;

One small pointer. That '1/8' is what you get when you evaluate a hash in a scalar context. The 8 is the number of buckets assigned to the hash and the 1 is the number of buckets that are in use.
It's generally useless, other than as a flag that you're doing something wrong.

While the intent of the question makes me cry, the difference between your two code snippets has nothing to do with do or eval and everything to do with context. And since that is a legitimate Perl topic, I'll briefly answer it.
In
my $tempref = eval $schemafile;
, the eval takes place in scalar context (imposed by the assignment to $tempref). $schemafile, however, contains a hash, created by the hash reference dereference operator %{}. When that hash is evaluated as a scalar it produces 1/8, normal behavior for a hash.
In
print Dumper do "/tmp/schemawithinschema.pl";
, the do takes place in the list context imposed by the Dumper call (which in turn is in the list context of the print). do creates the same hash that the eval did, but now it's being evaluated in list context, in fact as a list of arguments to Dumper. The top-level hash gets flattened into a list of Label => HashRef pairs, but that's not enough to stop Dumper from being able to show you something that looks a lot like the hash you were trying to create.
For future reference, it is helpful when trying to pinpoint a strange difference of behavior to present exactly the same call in both cases. The more variables between two test cases, the more things that you weren't expecting to matter will wind up mattering and confuse you.
All of that said, the real answer to "What's wrong here?" remains "Trying to do this at all.".

Related

How to print an element from an array which is inside the hash in perl

I'm trying to print the outputs from an API which are in multidimensional format.
use strict;
use warnings;
use Data::Dumper;
my $content={
'school_set' => 'SSET1234',
'result' => [
{
'school_name' => 'school_abc',
'display_value' => 'IL25',
'school_link' => 'example.com',
'status' => 'registerd',
'status_message' => 'only arts',
'school_id' => '58c388d40596191f',
}
],
'school_table' => 'arts_schools'
};
print "school_name is=".$content{result}[0]{school_name};
print "school_status is=".$content{result}[3]{status};
output
Global symbol "%content" requires explicit package name (did you forget to declare "my %content"?) at test8.pl line 20.
Global symbol "%content" requires explicit package name (did you forget to declare "my %content"?) at test8.pl line 21.
I have to print the outputs like below from the result.
school_name = school_abc
school_status = registered
If $content is a hash reference, you need to dereference it first. Use the arrow operator for that:
$content->{result}[0]{school_name}
The syntax without the arrow is only possible for %content.
my %content = ( result => [ { school_name => 'abc' } ] );
print $content{result}[0]{school_name};
If you want to print all the results, you have to loop over the array somehow. For example
#!/usr/bin/perl
use warnings;
use strict;
my $content = {
'result' => [
{
'school_name' => 'school_abc',
'status' => 'registerd',
},
{
'school_name' => 'school_def',
'status' => 'pending',
}
],
};
for my $school (#{ $content->{result} }) {
print "school_name is $school->{school_name}, status is $school->{status}\n";
}
Your data structure assumes an array, perhaps it would be useful to utilize loop output for the data of interest.
The data presented as hash reference and will require de-referencing to loop through an array.
Following code snippet is based on your posted code and demonstrates how desired output can be achieved.
use strict;
use warnings;
use feature 'say';
my $dataset = {
'school_set' => 'SSET1234',
'result' => [
{
'school_name' => 'school_abc',
'display_value' => 'IL25',
'school_link' => 'example.com',
'status' => 'registerd',
'status_message' => 'only arts',
'school_id' => '58c388d40596191f',
}
],
'school_table' => 'arts_schools'
};
for my $item ( #{$dataset->{result}} ) {
say "school_name is = $item->{school_name}\n"
. "school_status is = $item->{status}";
}
exit 0;
Output
school_name is = school_abc
school_status is = registerd

Deleting Key from 2D Hash in Perl

My Hash looks like this
%hIDSet = (
'TSASD2' => {
'country' => 'US',
'newid' => 'IMRAN',
'oldid' => 'TSASD4'
}
'TS767' => {
'country' => 'DE',
'newid' => 'B90LKT',
'oldid' => '432553'
},
);
when I do
my $sID = "TSASD2";
delete $hIDSet{$sID};
The output I get is
%hIDSet = (
'TSASD2' => {},
'TS767' => {
'country' => 'DE',
'newid' => 'B90LKT',
'oldid' => '432553'
},
);
My question is why the ID is not deleted completely?
You did delete the key, so you must have recreated it before dumping the hash again as in the following snippet:
my $sID = "TSASD2";
my %hIDSet = ( $sID => {} );
delete $hIDSet{$sID};
print(Dumper(\%hIDSet)); # It's gone
if ($hIDSet{$sID}{foo}) { '...' }
print(Dumper(\%hIDSet)); # You've recreated it.
Keep in mind that
$hIDSet{$sID}{foo}
is short for
$hIDSet{$sID}->{foo}
and that
EXPR->{foo}
means
( EXPR //= {} )->{foo}
so
$hIDSet{$sID}{foo}
means
( $hIDSet{$sID} //= {} )->{foo}
Note that this can assign to $hIDSet{$sID}.
Cannot reproduce.
You are confusing the syntax for Perl hashes, and hashref literals. This signifies a hashref:
use Data::Dumper;
my $hashref = {
foo => 'bar', # ← note comma between items
baz => 'qux',
};
delete $hashref->{foo};
print Dumper $hashref;
# $VAR1 = { baz => "qux" };
On the other hand, hashes are just lists:
use Data::Dumper;
my %hash = ( # note parens
foo => 'bar',
baz => 'qux',
);
delete $hash{foo};
print Dumper \%hash;
# $VAR1 = { baz => "qux" };
The code your provided shouldn't compile because of a missing comma, and would fail to run with use strict; use warnings; because of the hash–hashref mismatch. Clean up the types, and it should work allright.

Perl Hash of Hashes Storing References To Within - Can't use string ("") as a HASH ref

Alright I have a function which generates a hash tree that dumper prints out to look like this:
$VAR1 = {
'shaders' => {
'stock_gui.vert' => '',
'stock_font.vert' => '',
'stock_gui.frag' => '',
'stock_font.frag' => ''
},
'textures' => {},
'fonts' => {
'DroidSansMono.ttf' => '',
'small' => {
'DroidSansMono.ttf' => ''
}
}
};
Now I am trying to dfs iterate for example the fonts sub tree:
push (#stack, \%{$myHash->{'fonts'}});
Then in a loop:
my $tmpHash = pop(#stack);
Then a dumper of $tmpHash shows:
$VAR1 = {
'DroidSansMono.ttf' => '',
'small' => {
'DroidSansMono.ttf' => ''
}
};
The problem is trying access the children of the hash reference. I can count the keys and see the children. The dumper output looks okay. However trying to do something like:
foreach my $childKey ( keys $tmpHash ){
my $subChildrenCount = scalar keys(%{$tmpHash->{$childKey}});
}
Yields the error:
Can't use string ("") as a HASH ref while "strict refs" in use
I think this is because $tmpHash is a hash reference. I likely just need to dereference it somewhere. I've tried many things and all yields further issues. Any help appreciated.
If I try:
%{$tmpHash->{'small'}}
Then it works fine.
UPDATE:
If the string contains a '.' then this error occurs. Hard coding 'small' works. Hard coding 'stock_gui.vert' fails unless I escape the '.'. However the keys do not match if I escape the dot...
As you can see by running it yourself,
use strict;
use warnings;
my $tmpHash = {
'DroidSansMono.ttf' => '',
'small' => {
'DroidSansMono.ttf' => ''
}
};
my $subChildrenCount = scalar keys(%{$tmpHash->{'small'}});
the code you say gives that error does not actually give that error. I suspect you are actually doing
my $subChildrenCount = scalar keys(%{$tmpHash->{'DroidSansMono.ttf'}});
Your hash format doesn't make much sense. It mixes field names and actual data as keys.

How do you get MotherDogRobot to birth an array of puppy objects using map and a hash of hashes?

Puppy meta data gets read in from config file using (General::Config) and creates this hash of hashes
$puppy_hashes = {
puppy_blue => { name => 'charlie', age => 4 },
puppy_red => { name => 'sam', age => 9 },
puppy_yellow => { name => 'jerry', age => 2 },
puppy_green => { name => 'phil', age => 5 },
}
the MotherDogRobot package consumes the puppies hash to birth an array of puppy objects (lol)
package MotherDogRobot;
use Moose;
use Puppy;
use Data::Dumper;
#moose includes warn and strict
sub init_puppy{
my($self,%options) = #_;
my $puppy = Puppy->new( %options );
return ($puppy);
}
sub birth_puppies{
my($self,$puppy_hashes) = #_;
my #keys = keys %{$puppy_hashes};
my #puppies = map { $self->init_puppy( $puppy_hashes->{$_} ) } #keys;
return(#puppies);
}
sub show_me_new_puppies{
my($self,$puppy_hashes) #_;
print Dumper($self->birth_puppies($puppy_hashes));
}
Error odd number of arguments
passing %options to Puppy->new(%options)
no luck birthing puppies -- which means I can't put lasers on their heads =/
UPDATE
I think the problem is that I'm passing a Hash Ref to init_puppy() instead of an array or hash, so when I try to pass %options to the new constructor, it's not getting a proper ( key => value) pair -- hence the odd number of arguments error.
But from this standpoint I've been looking at this code too long I cant figure out how to dereference this properly.
btw this is my official day 22 of using Perl!
you're using empty variables as if they're not empty, that is, you're not doing anything at all
print "hi $_ " for my #foo;
This assumes that the incomplete snippet you've shown is what you're really using
update: Similarly in sub init_puppy, you never initialize my($self,%options)=#_;
#!/usr/bin/perl --
use strict;
use warnings;
Main( #ARGV );
exit( 0 );
sub Main {
my $puppy_hashes = {
puppy_blue => { name => 'charlie', age => 4 },
puppy_red => { name => 'sam', age => 9 },
puppy_yellow => { name => 'jerry', age => 2 },
puppy_green => { name => 'phil', age => 5 },
};
for my $puppy ( MotherDogRobot->birth_puppies($puppy_hashes) ) {
print join ' ', $puppy, $puppy->name, $puppy->age, $puppy->dump, "\n";
}
}
BEGIN {
package Puppy;
BEGIN { $INC{'Puppy.pm'} = __FILE__; }
use Any::Moose;
has 'name' => ( is => 'rw', isa => 'Str' );
has 'age' => ( is => 'rw', isa => 'Int' );
package MotherDogRobot;
BEGIN { $INC{'MotherDogRobot.pm'} = __FILE__; }
use Moose;
use Puppy;
sub init_puppy {
my ( $self, %options ) = #_;
my $puppy = Puppy->new(%options);
return ($puppy);
}
sub birth_puppies {
my ( $self, $puppy_hashes ) = #_;
my #puppies = map { $self->init_puppy( %{$_} ) } values %$puppy_hashes;
return (#puppies);
}
no Moose;
}
The standard Moose constructor will accept both
->new( %{ $puppy_hashes->{$_} } )
and
->new( $puppy_hashes->{$_} )
if $puppy_hashes contains what you say it does, and $_ is an existing key.
Furthermore, Moose will not give the error Error odd number of argments when you pass no arguments. (You're not assigning anything to %config.)
I can't tell which part of what you said is wrong, but what you said doesn't add up.

Is this snippet creating an anonymous Perl hash?

While reading the snippets provided by FormFiller ( where I kinda got by accident ) , I noticed this line:
$f->add_filler( password => Interactive => []);
Is this password => Interactive => [] equivalent to {"password" => {"Interactive"=>[]}}? If not, what does it become?
=> is semantically (almost) identical to , (see "Comma operator" at perldoc perlop), so you're doing this:
$f->add_filler( 'password', 'Interactive', [] );
If this calling style is supported by the method (which it doesn't), then it itself would have to convert these arguments into
{ password => { Interactive => [] } }
However more typically, hash-style arguments would be passed as a legal hash to begin with:
$f->add_filler( password => { Interactive => 1 } );
This would be received by the function like this:
sub add_filler
{
my $this = shift;
my %configs = #_;
# ...
}
The Data::Dumper module is great for answering questions like this. Use the following mock:
package Foo;
use Data::Dumper;
sub new { bless {} => shift }
sub add_filler {
my $self = shift;
print Dumper \#_;
}
Then call it
package main;
my $f = Foo->new;
$f->add_filler( password => Interactive => []);
and see when you get:
$VAR1 = [
'password',
'Interactive',
[]
];
This shows that add_filler receives a flat list of three arguments: two strings and a reference to an anonymous array.
No, it's exactly the same as
$f->add_filler( "password", "Interactive", []);