Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

"Is it a hashref" vs "Can I use it like a hashref?"

by blokhead (Monsignor)
on Jan 19, 2004 at 22:57 UTC ( [id://322466]=perlmeditation: print w/replies, xml ) Need Help??

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

Replies are listed 'Best First'.
Re: "Is it a hashref" vs "Can I use it like a hashref?"
by chromatic (Archbishop) on Jan 19, 2004 at 23:12 UTC

    Arguably HTML::Template should use methods on those objects instead of poking around in their guts... but I agree with your greater premise. Class::Roles is a simple implementation of Perl 6's answer to this question.

    Update: Of course, if HTML::Template's documentation asks you to pass in hashrefs and doesn't say that you can pass in objects, you're getting what you deserve. :)

Re: "Is it a hashref" vs "Can I use it like a hashref?"
by dragonchild (Archbishop) on Jan 20, 2004 at 00:33 UTC
    This very concept was discussed in this thread, with this very concept brought up by me here, as it pertains to ref and ARRAY. I have an object that is implemented as an blessed arrayref, but overloads stringification (and the other -ifications, but that's beside the point). I want HTML::Template to treat it as a scalar, cause it quacks like a scalar. But, H::T says "You have implemented this thing as an ARRAY, so you must be wanting it in a TMPL_LOOP, not a TMPL_VAR". So, my solution was to overload isa() in my class, as so:
    sub isa { my $self = shift; my ($type) = @_; my %is_builtin = map { $_ => 1 } qw( HASH ARRAY SCALAR CODE GLOB ); return 0 if $is_builtin{$type}; return $self->SUPER::isa($type); }

    Both your method and mine have their advantages and disadvantages. I like yours for modules that need to care. I prefer mine when dealing with modules that care how stuff is implemented, like H::T.

    Note about yours - it would be better if you didn't do explicitly dereferencing as the only option. It doesn't deal with stringification as provided by overload. I would prefer to see can() expanded to handle overload. For example, instead of asking if it overloads scalar dereferencing (which my object doesn't and shouldn't), you should be able to ask if it provides a stingification method. (The standard stringification wouldn't count. If you wanted to allow that, you shouldn't be asking the question.) Something along the lines of:

    sub can_stringify { my ($thingy) = @_; return 1 unless ref $thingy; return 0 unless blessed $thingy; return $thingy->can('STRINGIFY'); }

    That kind of code could be provided as part of can(). So, you could do something like if (UNIVERSAL::can($thingy, 'STRINGIFY')) { ... }, similar to how one tests an unknown scalar if it's a certain type, using UNIVERSAL::isa().

    In other words, I think the problem can be solved by extending overload.

    ... does some source-diving ...

    Hmmm ... Apparently, there is a way to test for these things using can. The stringification method is called ("". So, doing something like will work:

    if (UNIVERSAL::can($x, 'can') && $x->can('(""')) { # Do stuff here }
    (I've tested it.)

    There're also methods called OverloadedStringify() and mycan(), but I couldn't make it work in 5 seconds. I think the can() method probably works best. Ideally, overload would provide constants that can be used for determining overloaded-ness, instead of '(@{}' (which is the overloaded method for array dereference).

    There are other neato gems in overload. For example, there's an Overloaded() method, which tells you if the class has been overloaded.

    ------
    We are the carpenters and bricklayers of the Information Age.

    Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

Re: "Is it a hashref" vs "Can I use it like a hashref?"
by ysth (Canon) on Jan 20, 2004 at 00:25 UTC
    Beware of just saying use overload; when you want to use some of the functions in the package. It will set up some of the overloading stuff in your package, with perhaps hard to track down effects. If you just want to use subs in overload, say use overload ();.
Re: "Is it a hashref" vs "Can I use it like a hashref?"
by stvn (Monsignor) on Jan 20, 2004 at 02:53 UTC

    IMHO, if HTML::Template were to allow such a wide open definition of what it accepts, it would be a support nightmare. Accepting every tied and overloaded data-structure would certainly degrade performance for plain old everyday hash-refs.

    Personally, I think HTML::Template is fine as it is. It's very stable, extremly usable and has a simple and well defined API/interface. Subclass it if you want it to do these things.

    I actually think your argument is more with tie than it is with HTML::Template. ( Overloading the %{} operator still requires you to return a hash-ref. If you dont want to return a real hash-ref, then it needs to be a tied hash-ref (see the overload doc for more info) ) What I think you really want is for tie to say "i am a hash" rather than "you can treat me like a hash". But to be honest, I am not a fan of that approach though.

    -stvn
Re: "Is it a hashref" vs "Can I use it like a hashref?"
by Anonymous Monk on Jan 19, 2004 at 23:14 UTC
    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?"
    Well you're wrong. HTML::Template deals with data (perl primitives), not objects (even though it'll take a hashref based one).
      Objects are not different than primitive Perl datastructures. I guess I was a little loose with my usage of the word "object" in that quote, but I simply meant "a thing" and not specifically a blessed reference. HTML::Template currently will take any hashref, blessed or unblessed. But the point is that H::T doesn't hinge on any internal hash magic -- all it needs from the "things" you pass is the interface of a hashref. A blessed arrayref can have this interface, but the way HTML::Template checks its input makes it very hard to successfully get this to work.

      The documentation for HTML::Template says to pass it in hashrefs, etc -- "plain old reference" datastructures. But blessed objects can be used seamlessly as "plain old reference" datastructures, either by being the correct type of blessed reference, or by overloading the appropriate dereferencing operators. I'm just suggesting a more complete way of checking for the type of a datastructure that includes these possibilities.

      It's like writing a sub that requires a scalar, so you have some code to verify the input is a scalar. But the verification code fails for a tied scalar. That would be ridiculous. We have tied objects so we can use the "plain old variable" interface seamlessly. We also have the ability to overload dereferencing operators -- for what other reason than to use the "plain old reference" interface?

      blokhead

        We have tied objects so we can use the "plain old variable" interface seamlessly
        Which raises an interesting question. Why didn't you just tie the hash you wanted to pass to H::T?

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://322466]
Approved by jweed
Front-paged by jweed
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others rifling through the Monastery: (4)
As of 2024-04-23 20:55 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found