I have a hash with a few values that are not scalar data but rather anonymous subroutines that return scalar data. I want to make this completely transparent to the part of the code that looks up values in the hash, so that it doesn't have to be aware that some of the hash values may be anonymous subroutines that return scalar data rather than just plain old scalar data.
To that effect, is there any way to have the anonymous subroutines executed when their keys are accessed, without using any special syntax? Here's a simplified example that illustrates the goal and the problem:
#!/usr/bin/perl
my %hash = (
key1 => "value1",
key2 => sub {
return "value2"; # In the real code, this value can differ
},
);
foreach my $key (sort keys %hash) {
print $hash{$key} . "\n";
}
The output I would like is:
perl ./test.pl
value1
value2
Instead, this is what I get:
perl ./test.pl
value1
CODE(0x7fb30282cfe0)
One of perl's special features for just such a use case is
tie
. This allows you to attach object oriented style methods, to a scalar or hash.It should be used with caution, because it can mean that your code is doing really strange things, in unexpected ways.
But as an example:
As you can see - this lets you take an 'ordinary' scalar, and do fruity things with it. You can use a very similar mechanism with a
hash
- an example might be to do database lookups.However, you also need to be quite cautious - because you're creating action at a distance by doing so. Future maintenance programmers might well not expect your
$random_var
to actually change each time you run it, and a value assignment to not actually 'set'.It can be really useful for e.g. testing though, which is why I give an example.
In your example - you could potentially 'tie' the hash:
This is slightly less evil, because future maintenance programmers can use your 'hash' normally. But dynamic eval can shoot the unwary in the foot, so still - caution is advised.
And alternative is to do it 'proper' object oriented - create a 'storage object' that's ... basically like the above - only it creates an object, rather than using
tie
. This should be much clearer for long term usage, because you won't get unexpected behaviour. (It's an object doing magic, which is normal, not a hash that 'works funny').You need to identify when a code ref is present, then execute it as an actual call:
Note that you may consider making all of the hash values subs (a true dispatch table) instead of having some that return non-coderefs and some that return refs.
However, if you define the hash as such, you don't have to do any special trickery when it comes time to use the hash. It calls the sub and returns the value directly when the key is looked up.
I don't believe that the words that others have written in disapproval of the
tie
mechanism are warranted. None of the authors seem to properly understand how it works and what core library backup is availableHere's a
tie
example based onTie::StdHash
If you tie a hash to the
Tie::StdHash
class then it works exactly as a normal hash. That means there's nothing left to write except for methods that you may want to overrideIn this case I've overridden
TIEHASH
so that I could specify the initialisation list in the same statement as thetie
command, andFETCH
, which calls the superclass's FETCH and then makes a call to it if it happens to be a subroutine referenceYour tied hash will work as normal except for the change that you have asked for. I hope it is obvious that there is no longer a direct way to retrieve a subroutine reference if you have stored it as a hash value. Such a value will always be replaced by the result of calling it without any parameters
SpecialHash.pm
main.pl
output
Update
It sounds like your real situation is with an existing hash that looks something like this
Using
tie
on already-populated variables isn't so neat as tying then at the point of declaration and then inserting the values as the data must be copied to the tied object. However the way I have written theTIEHASH
method in theSpecialHash
class makes this simple to do in thetie
statementIf possible, it would be much better to
tie
each hash before you put data into it and add it to the primary hashThis program ties every value of
%hash
that happens to be a hash reference. The core of this is the statementwhich functions identically to
in the previous code but dereferences
$val
to make the syntax valid, and also uses the current contents of the hash as the initialisation data for the tied hash. That is how the data gets copiedAfter that there is just a couple of nested loops that dump the whole of
%hash
to verify that the ties are workingoutput
Yes you can. You can either
tie
hash to implementation that will resolve coderefs to their return values or you can use blessed scalars as values withoverload
ed mehods for stringification, numification and whatever else context you want to resolve automatically.There's a feature called "magic" that allows code to be called when variables are accessed.
Adding magic to a variable greatly slows down access to that variable, but some are more expensive than others.
tie
is an more expensive form of magic, and it's not needed here.As such, the most efficient solution is the following:
Output:
Other examples:
You can also "fix up" an existing hash. In your hash-of-hashes scenario,
As noted by Oleg, it's possible to do this using various more or less arcane tricks like
tie
, overloading or magic variables. However, this would be both needlessly complicated and pointlessly obfuscated. As cool as such tricks are, using them in real code would be a mistake at least 99% of the time.In practice, the simplest and cleanest solution is probably to write a helper subroutine that takes a scalar and, if it's a code reference, executes it and returns the result:
and use it like this: