I was playing around the other day, seeing how I could make some objects play nicer with HTML::Template. I didn't want to just use blessed hashrefs as the object implementation, because I didn't want all the private data visible to H::T. My idea was to use blessed arrayrefs, but overload %{} so H::T could use them as hashrefs. This way, I could still control which keys were visible.

I passed some of these objects into H::T but it didn't work, failing with Single reference arg to param() must be a hash-ref! You gave me a Foo. Dang! I looked in the code and saw that it uses UNIVERSAL::isa to check when it expects a data structure to be a hashref:

my $type = ref $first; ... croak("...") unless $type eq 'HASH' or (ref($first) and UNIVERSAL::isa($first, 'H +ASH'))
Well, this is a bummer, I thought. UNIVERSAL::isa (among its other shortcomings for determining the type of a reference) doesn't take overloading into account. I finally had to declare an empty package HASH and put it in @Foo::ISA to get the objects to work cleanly with H::T. Yuck. Not a very elegant solution at all.

It dawned on me that asking "is this thing really a hashref?" seems like poor coding -- we are demanding to know something about the implementation of an object. Instead, we should only need to ask things about the object's interface, like "can I use this thing like a hashref?" After thinking about it, I would guess that 90% of the time someone asks that first question, their intent would be better captured by asking the second question. Hopefully the example above with HTML::Template illustrates this.

An easy way to check for hash-deref-ability would be to try eval { %{$thing} } and check for failure in $@. But if $thing is overloaded for %{}, this operation could potentially have side-effects for $thing. It would be better to have a way that doesn't introduce possible Heisenberg problems.

This is what I came up with, using utility functions from Scalar::Util and overload:

use overload (); # added (), according to ysth's suggestion use Scalar::Util qw/blessed reftype/; use Carp; ## call this function as: can_deref_as($ref, $type) sub can_deref_as { my ($thing, $type) = @_; my %overload = ( HASH => '%{}', ARRAY => '@{}', SCALAR => '${}', GLOB => '*{}', CODE => '&{}' ); croak "Invalid reference type $type" unless $overload{$type}; return 0 unless ref $thing; return 1 if $type eq reftype $thing; return 1 if blessed $thing and overload::Method($thing, $overload{$type}); return 0; }
It handles overloaded cases correctly, and is not fooled by the same silly cases (ie, bless [] => "HASH") that ref and UNIVERSAL::isa are.

I wonder if this function should be included in Scalar::Util (or put in a separate module somewhere), I may email the maintainer to get his opinion. In any case, it would sure be nice for modules like HTML::Template to check for hash-deref-ability instead of checking the true reference type, so we can all use objects with hash-dereferencing overloaded.

What are your thoughts?

blokhead


In reply to "Is it a hashref" vs "Can I use it like a hashref?" by blokhead

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.