One interesting thing I've got stucked on - if we have some multilevel arrays/hashes (which aren't the same in perl), there is no standard way to change all the object's values. The only thing I've found usefull is
map function, which nevertheless works only for map arrays. However, it could be very usefull, to have a possibility to change each scalar element of some multilevel object. For example, if you ever used XML::Simple, you could observe the following behavior with values, which contain new lines.
XML:
<root>
<elem>asdf</elem>
</root>
Check, how it looks after read in:
BASH:
user@host:~$ perl -e 'use Data::Dumper; use XML::Simple; $h = XMLin("<root><elem>asdf</elem></root>"); print Dumper($h);'
$VAR1 = {
'elem' => 'asdf'
};
Now change the XML a little bit, and check the difference:
XML:
<root>
<elem>
asdf
</elem>
</root>
The XML is still valid, but check, how it looks after read in:
BASH:
user@host:~$ perl -e 'use Data::Dumper; use XML::Simple; $h = XMLin("<root><elem>\nasdf\n</elem></root>"); print Dumper($h);'
$VAR1 = {
'elem' => '
asdf
'
};
But normally, one wants only to have the value, without newlines.
So then, I've decided to spend some time and to write a subroutine, which could map each type of the data (hm, not really each, only array, hash or scalar). So I've walked abstract up, to make the routine more generic. It's the core of this article:
PERL:
sub obj_map
{
my $obj = shift;
my $fn = shift;
if( "HASH" eq ref $obj ) {
while( my ( $k, $v ) = each %$obj ) {
$obj->{$k} = obj_map( $v, $fn, @_ );
}
} elsif ( "ARRAY" eq ref $obj ) {
my $i = 0;
foreach my $v ( @$obj ) {
@$obj[$i++] = obj_map( $v, $fn, @_ );
}
} else {
return &$fn( $obj, @_ );
}
return $obj;
}
As you can see, I'm using the reference to an external function, which will be invoked for each scalar value. And it works with the rest of the passed arguments as with the &$fn arguments, simply passing them further.
And now, you had, for example, a data structure like this:
PERL:
my $o = {
"hash_key[0]" => [
"array_element[0]",
{ "hash_key[0]" => "&hash_val[0]", "hash_key[1]" => '$hash_val[1]' }
],
"hash_key[1]" => "hash_key[1]"
};
and presume, you wanna escape each scalar value (even in multilevels), using the subroutine:
PERL:
sub escape
{
my $s = shift;
$s =~ s/([\\\^\$\{\}\[\]\(\)\.\+\?\|\-\&]{1,})/\\$1/gi;
return $s;
}
The simplest way to do this is to call the obj_map sub like this:
PERL:
$o = obj_map( $o, \&escape );
Or you could incapsulate this call in some sub with more specific name, eq.:
PERL:
sub escape_ex
{
my $o = shift;
return obj_map( $o, \&escape );
}
$o = escape_ex( $o );
Or you could spare the escape sub packing it at the runtime in an anonymous one:
PERL:
$o = obj_map( $o, \&{ sub { my $s = shift; $s =~ s/([\\\^\$\{\}\[\]\(\)\.\+\?\|\-\&]{1,})/\\$1/gi; return $s; } } );
A more complicated example I wanna to bring here is the derivative calculation.
So this is our simple function:
PERL:
But to not over complicate the example, I've maked it to take one argument only. Now our derivative and derivative_ex subs:
PERL:
sub derivative
{
my ( $arg, $fn, $quotient ) = @_;
my ( $f, $fh );
return undef until defined $fn && defined $quotient && defined $arg;
$f = &$fn( $arg);
$fh = &$fn( $arg + $quotient);
return undef until defined $f && defined $fh;
return ( $fh - $f ) / $quotient;
}
sub derivative_ex
{
my ( $o, $quotient, $fn ) = @_;
return obj_map( $o, \&derivative, $fn, $quotient );
}
And the appropriate call:
PERL:
$o = [ 1, 2, 3, 4, 5, 6 ];
$o = derivative_ex( $o, .0001, \&func );
Note, in this case the derivative will return undef for the value 5, because the function isn't exists at this point. But this may be not always correct.
So exactly this way we can use the obj_map to trim the newlines in the objects, which was read in with XMLin.
In general, we can apply the obj_map in all the situations, where we need to impact on all the scalar values of an multilevel and/or multitype object. But keep also in the mind - if you want to use it for blessed objects, the package name will be returned instead of HASH or ARRAY keyword (see
ref).