in reply to Is "ref $date eq 'ARRAY'" wrong?

Figure out another way. Really. Like, it's ridiculous that ref is still being used in high-level code. And, I've got a really good reason.

I have the following situation:

So far, so good, right? The problem is in the <readmore>

HTML::Template uses ref all over the place. One thing is does is check to see if the value of a parameter is an arrayref. If it is, then it assumes that you're using it for a <TMPL_LOOP> and complains if it's being used for a <TMPL_VAR>. My self-formatting nubmers are meant for <TMPL_VAR> usage. So, we have a case of where I have to do this in my self-formatting number class. (I should move it to the base class, frankly.)

{ my %is_internal_type = map { $_ => 1 } qw( ARRAY HASH SCALAR GLOB CODE ); sub isa { my $self = shift; my ($type) = @_; return if $is_internal_type{$type}; return $self->SUPER::isa($type); } }

All because HTML::Template uses ref. And, it's not just HTML::Template. Most modules that have much dwimmery suffer from the same ... lack of discernment.

Figure out another way. Use class names and provide a factory, use different function name to provide different signatures, anything! Just don't use ref!

------
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.

Replies are listed 'Best First'.
Re: Why ref() is bad.
by BrowserUk (Patriarch) on Dec 19, 2003 at 18:48 UTC

    An alternative viewpoint is that HTML::Template was written to accept a scalar or an arrayref and deal with these appropriately. By overloading your object (array) references to act as scalars in a scalar context, and then passing these to HTML::Template, you are creating the problem and asking for pre-existing code to fix it.

    Couldn't you be the one to provide a separate function, $obj->toString(), and use that rather than overloading to pass the stringified value of your object to HTML::Template?

    ( Also, would $template->function( scalar $obj_to_stringify ); 'fix the problem'? ).

    Note: I'm playing devil's advocate here because this is one of those issues about which I am undecided.

    Not using ref, or context sensitivity in code seems like were throwing away a fundamental part of what makes perl, perl.

    On the other hand, maybe the dwimery that makes writing small, procedural code in perl such a pleasure, is simply at odds with writing larger, more complicates app where OO comes into it's own?


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    Hooray!

      You're absolutely correct, in that the code is documented as to what it does. However, there is a disconnect between the idea of an array reference and an object which just so happens to be implemented as an array reference.

      ... reads what he just wrote ...

      SIGH

      As a Perl hacker, I have problems with the twit who wrote the first part of this reply. I can easily see someone wanting to do something like:

      package My::Overloaded::Array; use overload '+=' => 'my_push'; sub my_push { my $self = shift; push @{$self}, @_; }

      Now, the hapless coder wants to have HTML::Template use it as an array. I have an object which is an arrayref and I want HTML::Template to treat it as a plain scalar that isn't a reference. And, frankly, there's no way for poor HTML::Template to tell the difference!

      ... thinks some more ...

      Unless our intrepid array-hacking hero overloads @{} (or whatever it's called these days ... []?) ... Or, I'm stuck with my solution of overriding isa(). What are your thoughts? (Maybe there's a meditation in all this rambling I seem to be doing of late ...)

      ------
      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.

        To be honest, I am not yet sure what the best answer is. In general, I've stayed out of discussions regarding how best to use OO in perl (5) simply because I haven't seen a truely complete approach to putting OO together with perls dwimery such that you don't end up with dilemmas like this.

        This sort of leads to the same set of thoughts that I had in my mind when I wrote this earlier today.

        I've played with various OO techniques and modules from CPAN, but I invariably run up against some barrier, limitation, logical or syntactic inconsistancy that prevents me from getting the interfaces to work quite the way I would like them to.

        • I like overloaded operators, where they make sense.

          I find $complex1 += $complex2; infinitely preferable to

          $complex1->set( $complex1->add( $complex2->get() );
        • I like lvaluable (logical) attributes for objects.

          I prefer

          $file->position = 0 if $file->eof;

          to

          $file->set_position( 0 ) if $file->eof();
        • I like the idea of context sensitivity, on both input and output.

          To my way of thinking the value of this is similar to one of the reasons we use OO in the first place. It is a form of namespace simplification. Polymorphism if you will.

          One of the reasons for using OO, is that it allows us to re-use verbs as function (method) names without having to further qualify those names. I well remember writing C code where every library had its entrypoints prefixed with the name of the datastructure it operated on. thingy_init(), thingy_create(), thingy_destroy() and so on so as to prevent name conflicts. OO removes the need for this.

          Avoiding the need for

          $thingy->set_date_from_string("..."); $thingy->set_date_from_array( localtime ); $thingy->set_date_from_array_ref( [localtime] );

          by using using runtime determination of passed types to a single method seems a logical extension of this.

          Likewise, return context.

        So far, my attempts at trying to put together a clean and consistant implementation of classes that utilise all of these techniques in the same class have always ended up with an inconsistancy, or requiring undesirable copy&paste coding somewhere down the line.

        Even simple things like inside-out objects leave me undecided. If your class has 3 physical attributes, do you code that as

        package MyClass; my %instances; sub new{ my( $class, $attr1, $attr2, attr3 ) = @_; my $self = bless {}, $class; return $instances{ $self } = [$attr1, $attr2, $attr3]; }

        or

        package MyClass; my( %attr1, %attr2, %attr3 ); sub new{ my( $class, $attr1, $attr2, attr3 ) = @_; my $self = bless {}, $class; $attr1{ $self } = $attr1; $attr2{ $self } = $attr2; $attr3{ $self } = $attr3; return $self; }

        The former is shorter and less profligate with space. The latter results in less complex syntax when referencing the attributes with the methods, but relies upon "parallel arrays", which I normally consider a bad idea. It also consumes more memory.

        The complicated syntax of the former can be diffused by using (private) accessors/mutator functions, but that adds to the performance overhead.

        If the objects are largish and few in number and not called in tight loops, neither is too much of a problem, but if the objects themselves are pretty small, but you need to use lots of them and/or call their methods in tight loops, then the overhead in memory and performance is a pain.

        Maybe the answer is to wait until P6 for a clean solution and just muddle through for now?

Re: Why ref() is bad. (It isn't bad, your assumptions are.)
by sauoq (Abbot) on Dec 20, 2003 at 05:13 UTC
    All because HTML::Template uses ref.

    Actually, no, that's not why. If it was just using ref() it wouldn't see that your object was an arrayref, it would see it as an object of some class. It uses isa(). This is exactly the advice the OP was getting: that he should use UNIVERSAL::isa(). I think that's generally poor advice and HTML::Template is one good example of one place just using ref() would have been an excellent idea.

    Here is the relevant crufty code from HTML::Template:

    if (defined($value_type) and length($value_type) and ($value_type +eq 'ARRAY' or ((ref($value) !~ /^(CODE)|(HASH)|(SCALAR)$/) and $value->isa('ARRA +Y')))) { (ref($param_map->{$param}) eq 'HTML::Template::LOOP') or croak("HTML::Template::param() : attempt to set parameter '$pa +ram' with an array ref - parameter is not a TMPL_LOOP!");

    I think you are really misplacing blame. You are using HTML::Template so, you've contracted to abide by its API. You can't expect HTML::Template to handle everyones quirky data, afterall. And I think that's the fundamental misconception here. It isn't the module author's responsibility to code for every contingency. It's his responsibility to develop an API that makes sense and stick to it. Keeping it simple is a sound approach!

    Your simple fix is to call

    $template->param( PARAM => "$object");
    In other words, stringify your object yourself. You're the one who knows it should be stringified. HTML::Template doesn't.

    -sauoq
    "My two cents aren't worth a dime.";
    
      Why is HTML::Template checking to see if I'm passing in an arrayref to a non-TMPL_LOOP param? Why should HTML::Template care?!? What if I wanted to see "ARRAY=(0x23453245)" as my value for some parameter. I can see a very good reason to do that - creating an online debugger. But, HTML::Template prevents me from doing that.

      Furthermore, HTML::Template doesn't check to see if the thing is truly a scalar. All it does is make sure the thing isn't an arrayref. The original version of my formatted number class used a hashref as its implementation. No problems. So, I figured that any implementation would work. And, in fact, any implementation would work ... except for arrayref. THAT is my complaint.

      Though, the fact that HTML::Template uses isa() makes sense, cause I "fixed" the problem by blocking isa() from reporting the object is any form of base type. You're right - ref wouldn't've caused the problem. I still stand why my statement - you shouldn't check to see if it is a duck ... you should check to see if it walks, talks, and smells like a duck. Deal with interface, not implementation. And, that can be done pretty simply.

      As for me knowing it should be stringified ... HTML::Template should trust me. If I say I want it to use X as a scalar, it should let me. It smacks too much of B&D, the way it does things now.

      ------
      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.

        Why is HTML::Template checking to see if I'm passing in an arrayref to a non-TMPL_LOOP param? Why should HTML::Template care?!?

        On this point, we agree. I think this was a poor decision on the part of HTML::Template's author. FWIW, I don't care for HTML::Template, I've never recommended it, and this is one of the reasons why. But, the issue you were discussing (note your title) and the conclusion you came to were not in sync with the example you gave.

        What if I wanted to see "ARRAY=(0x23453245)" as my value for some parameter. I can see a very good reason to do that - creating an online debugger. But, HTML::Template prevents me from doing that.

        No, it does not prevent you from doing that. It simply requires that you stringify your arguments yourself. Whether or not that should be required is really a moot point. The module provides a complete interface. If you don't like it, find another module or roll your own. But, TIMTOWTDI and you can't blame the author for picking one you don't like.

        You're right - ref wouldn't've caused the problem. I still stand why my statement - you shouldn't check to see if it is a duck ... you should check to see if it walks, talks, and smells like a duck. Deal with interface, not implementation. And, that can be done pretty simply.

        And using ref() is an effective way to do that. If ref() reports that a scalar is an array reference, then you can use it like one. If it reports that that it is an object of class Foo, then treating it like an array reference is dealing with the Foo-thing's implementation rather than its interface and should be avoided.

        You are complaining because you wrote a Foo class and you tried to make its interface appear to be like a builtin type. That's a questionable design decision. And the way you go about it is questionable too. Presumably, if you wanted it to act like an array, you could have written Tie::FooArray instead of abusing overloading.

        You say "deal with the interface, not the implementation", and I agree. But, that contract must be between programmers. You can't expect one module to discover and deal appropriately with the multitude of interfaces that exist. In Perl especially, enforcing such a contract is not the job of the language. In Perl, it's not even defined by the language, but by the documentation.

        As for me knowing it should be stringified ... HTML::Template should trust me.

        Any module, HTML::Template included, should only have to trust you to abide by its interface. It should not be required to make assumptions about what your data is, and it should be allowed to make decisions based upon what your data appears to be when checked with a builtin function such as ref().

        It smacks too much of B&D, the way it does things now.

        I don't buy that. A module can't be expected to support every overly clever interface that might come its way. It can only be expected to provide its own. The programmer's job is to glue them together.

        -sauoq
        "My two cents aren't worth a dime.";