http://qs1969.pair.com?node_id=500531

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

I've released a module named Array::AsHash which allows one to use references as hash keys (it actually does a lot more than this, but this is one of the features). Normally, this works fine:

my $array = Array::AsHash->new({array => \@array}); my $value = $array->get($some_reference);

It does this by checking the Scalar::Util::refaddr of the reference. However, this can fail if someone wants to override equality. In Python, you can get around this by overriding the __hash__() method of object. In Perl, we can't do this with unblessed references. Thus, the following will never succeed:

my $aref = [1,2,3]; my $array = Array::AsHash->new({ array => [ $aref => 'Ovid' ] }); print $array->get($aref); # prints "Ovid" print $array->get([1,2,3]); # fails

Ordinarily, this is fine. However, if we only care about the values a reference contains and not whether or not it's the same reference, we're stuck. I'd like to be able to pass the constructor a coderef which will determine equality of references beyond simply comparing their addresses. Things like the Test::More::is_deeply function or the useful Test::Deep module would be great, but they're tied to Perl's testing framework.

Can anyone offer suggestions?

For those wondering why I want to do this, I don't need this myself (yet), but I'm creating a flexible general purpose tool for folks and I'd like to give them the ability to override behavior as needed.

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re: Using references as hash keys
by tye (Sage) on Oct 16, 2005 at 04:09 UTC

    The whole concept of a hash is having a hashing function, a sort of check-sum. So an 'isEqual' function just doesn't enable making a hash.

    You can emulate a hash using a tree if you provide an 'isLessThan' function. But you'd have to implement an ordered tree rather than just use Perl's hashes.

    But just about any 'isEqual' function can be transformed into a 'getKey' function that returns a string such that isEqual($a,$b) iff getKey($a) eq getKey($b). And, you've surely realized, the result of getKey can be used as the key in a Perl hash.

    - tye        

Re: Using references as hash keys
by geektron (Curate) on Oct 16, 2005 at 06:57 UTC
    while i don't have any immediate insight, if you haven't already compared Array::AsHash to Tie::RefHash ( or Tie::RefHash::Nestable ) those modules might offer some other insight.

    those modules do something similar ( allow refs as hash keys ).

Re: Using references as hash keys
by xdg (Monsignor) on Oct 16, 2005 at 12:04 UTC
    Things like the Test::More::is_deeply function or the useful Test::Deep module would be great, but they're tied to Perl's testing framework.

    You could just extract the relevant code from is_deeply and provide a "deep_references" option in the constructor as to whether references should be compared by their refaddr or by their contents. For this, you'll need to keep a copy of the references, too, hashed on their refaddrs to be able to retrieve them for comparison. (I think you'll want a regular reference, not weak one -- and the semantic will then be that storing something under a reference increments the refcount for that reference.)

    The other way to go is more like tye's suggestion around the hashing function, so if you want deep references, you freeze their contents into a string and hash on that.

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Re: Using references as hash keys
by traveler (Parson) on Oct 16, 2005 at 14:36 UTC
    Maybe I am confused but what about
    my $aref = [1,2,3]; my @other = [1,2]; my $oref = \@other; my $array = Array::AsHash->new({ array => [ $aref => 'Ovid', $oref => 'traveler' ] }); ... push @other, 3; print $array->get($aref); # prints "Ovid" print $array->get([1,2,3]); # fails
    If your suggested changes make the second get work, what would it print?
Re: Using references as hash keys
by adrianh (Chancellor) on Oct 16, 2005 at 17:51 UTC
    Ordinarily, this is fine. However, if we only care about the values a reference contains and not whether or not it's the same reference, we're stuck. I'd like to be able to pass the constructor a coderef which will determine equality of references beyond simply comparing their addresses.

    Well, if you're using Perl's native hashes behind the scenes then you could just use Storable... maybe something like:

    use Storable; sub identity_of { my $thing = shift; local $Storable::canonical = 1; return ref $thing ? Storable::freeze( $thing ) : $thing; }; my $array = Array::AsHash->new({ array => [ [1,2,3] => 'Ovid' ], hash_on => \&identity_of, });

    If you want to go hard-core implement your own hashing behind the scenes and pass hash and equality functions :-)

    For those wondering why I want to do this, I don't need this myself (yet), but I'm creating a flexible general purpose tool for folks and I'd like to give them the ability to override behavior as needed.

    What was it you said recently about YAGNI?

      Preamble: I'm not convinced of XP. And the OP design makes me shake my head in wonder as to why one would want this behaviour.

      That said, I don't see this as YAGNI. YAGNI simply cannot apply to public APIs. For example, Tim Bunce may not need all the APIs provided by DBI, but users of DBI may, among all of them, need those APIs. Different APIs that do things the same way, but return their results differently (selectall_arrayref vs selectall_hashref vs ...) aren't "needed", but sure as heck provide significant amounts of syntactic sugar that simplify oft-used paradigms while leaving flexibility to do things slightly differently if you feel/have the need.

        And the OP design makes me shake my head in wonder as to why one would want this behaviour.

        I can think of some times in the past when something like this would have been useful to me. Having non-primitive ordered pairs can sometimes be useful.

        YAGNI simply cannot apply to public APIs. For example, Tim Bunce may not need all the APIs provided by DBI, but users of DBI may, among all of them, need those APIs. Different APIs that do things the same way, but return their results differently (selectall_arrayref vs selectall_hashref vs ...) aren't "needed"

        I think we're using different definitions of YAGNI. YAGNI isn't about having minimal interfaces. It's about avoiding the expense and potential error involved in writing code before a need for that code has been demonstrated.

Re: Using references as hash keys
by Moron (Curate) on Oct 18, 2005 at 13:56 UTC
    re "allows one to use references as hash keys", I didn't realise this was a problem so to be certain I tried this:
    #!/usr/bin/perl use Data::Dumper; my $var = "VARVALUE"; my @arr = ( 'ARRVAL1', 'ARRVAL2' ); my %hash = ( hashkey1 => hashval1, hashkey2 => hashval2 ); open my $fh, "| more"; my %HOfR = ( \$var => "valForRefOfVar", \@arr => "valForRefOfArr", \%hash => "valForRefOfHash", \&Sub => "valForRefOfSub", $fh => "valForRefOfGlob" ); print Dumper( \%HOfR ); sub Sub { };
    ...it produced the expected kind of results, i.e.:
    $VAR1 = { 'ARRAY(0x2ddf0)' => 'valForRefOfArr', 'SCALAR(0x2ddd8)' => 'valForRefOfVar', 'GLOB(0x228b4)' => 'valForRefOfGlob', 'HASH(0x2ddcc)' => 'valForRefOfHash', 'CODE(0x456cc)' => 'valForRefOfSub' };
    which makes me wonder what the original problem actually is.

    -M

    Free your mind

      Those references were converted to strings, which means that you can't get your reference back by looking at keys. But the more important issue (to the OP) was that identical anonymous arrayrefs will not map to the same hash element. That is, each time you mention $HOR{[1,2,3]}, you're talking about a different element.

      Caution: Contents may have been coded under pressure.