Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

•Re: It's a dog, but what kind? (polymorphism , in Perl OO)

by merlyn (Sage)
on Mar 23, 2004 at 19:55 UTC ( #339158=note: print w/ replies, xml ) Need Help??


in reply to It's a dog, but what kind? (polymorphism , in Perl OO)

my $class=ref($proto)||$proto;
See Why ref($proto) should be avoided and ref($proto) - just say no!, please. And read my column that hits it head on when the publisher's embargo is lifted. (This is for future reference.)

-- Randal L. Schwartz, Perl hacker
Be sure to read my standard disclaimer if this is a reply.


Comment on •Re: It's a dog, but what kind? (polymorphism , in Perl OO)
Download Code
Re^2: It's a dog, but what kind? (polymorphism , in Perl OO)
by tye (Cardinal) on Mar 23, 2004 at 20:42 UTC
    my $class=ref($proto)||­$proto;

    I understand the reasons to avoid this construct.

    But if you avoid it, then you need to replace it with something. Unfortunately, although I often see this construct derided, I very seldom see suggestions for what to replace that line with (even when I come out and ask for it). The most straightforward replacement is unacceptable to me. See Re^2: A few Perl OOP questions. (disparaging) for why.

    I also consider the objections to this to be rather minor in impact in a lot of practical situations and appreciate the "sloppy" advantages of $obj->new() in a lot of pracitcal situations. So I consider this construct to be a net win for simple OO Perl classes in many cases.

    I also provide an alternative to it in (tye)Re: Private Class Methods.

    - tye        

      From the article you can't see yet:
      But here's the problem. When I survey experienced object-oriented programmers, and ask them what they expect new means when called on an instance (without looking at the implementation), the result usually divides rather equally into three camps: those that go "huh, why would you do that" and think it should throw an error, those that say that it would clone the object, and those that say it would copy the object's class but not the contents.

      So, no matter what you intend if you make your new do one of those three things, two thirds of the people who look at it will be wrong. It's not intuitive. So, don't write code like that, and especially don't just cargo-cult that from the manpage into your code. If you want an object like another object, use ref explicitly, as shown above. If you want a clone, put cloning code into your package, and call clone, as we saw earlier.

      -- Randal L. Schwartz, Perl hacker
      Be sure to read my standard disclaimer if this is a reply.

        Yes, as I said, I understand the objection. I note that you don't quote what your alternative is nor acknowledge the problem that I was bringing up.

        Did you give a concrete class when you asked your question? As I've said, I agree that there is a problem in theory, and that sometimes there is a problem in practice. But I've also found that, in practice, exactly what it means to get a 'new' object from an object of a *specific* class is often quite clear (and I don't think it usually means what you appear to be referring to as "clone" nor "copy").

        You appear to be using "clone" to mean something close to "copy all of the object's attributes" and using "copy" (based on emphasis) to mean something close to "copy none of the object's attributes".

        I don't think I've ever seen people talking about classes as having two types of attributes (that I'll define shortly). So I'm not surprised that asking questions in the abstract fail to get people to think about splitting attributes into two types. In practice, for many Perl classes, I think this split happens quite naturally.

        I'll call the two types 'basis' attributes and 'convenience' attributes. The basis attributes are items that must be passed in to new(). The convenience attributes have more to do with the personal preferences of the user of the module.

        $obj->new( $basis1, $basis2, ... ) creates a new object based on the passed-in basis attributes but copying the convenience attributes from $obj.

        Now, some classes have attributes that don't clearly fall into one of these categories, and I suspect in that such cases the meaning of 'instance new' would not be as clear.

        And I suspect that people who think $obj->new( ... ) should be "clone" are either thinking of convenience attributes or aren't thinking of the "..." part while people who think $obj->new( ... ) should do ref($obj)->new( ... ) aren't thinking of convenience attributes (that perhaps are more common in Perl OO than in other flavors of OO).

        - tye        

Re: •Re: It's a dog, but what kind? (polymorphism , in Perl OO)
by flyingmoose (Priest) on Mar 23, 2004 at 20:49 UTC
    eval " use $breed; " ;

    Also, in true merlyn fashion, it looks like eval in this string context makes you have unsafe doggies if you are using your dogs on a web site and allowing input of the doggie type in a text field.

    $dog = ' strict; doEvil(); ';

    I also think the strange use of dog as a factory class to produce subtypes of dogs is a little odd-form from an OO form, and that most OO purists would frown on the eval and use of reflection to pick subclasses. But I come from a very non-Perlish OO background, so this is just my two cents.

    In conclusion, if we want to call this a tutorial, I say this should be a tutorial on the factory pattern, not a tutorial on polymorphism. Polymorphism is a much more general concept, and we skip over that generality by starting with the factory piece first. I would be interested to see if the factory could be made without using eval, as well...I think it can, especially if the dogs were loaded previously in a more-safe matter. But hey, maybe we don't have to worry about unsafe loading of doggies -- they do allright in the back of a pickup truck usually :)

    Anyway, cool stuff, just a few ideas thrown out here. *yelp*.

      Polymorphism is a much more general concept, and we skip over that generality by starting with the factory piece first.

      See my reply below for why this isn't a good example of polymorphisim anyway. Update: blue_cowdawg's update fix the OP's polymorphisim issue.

      I would be interested to see if the factory could be made without using eval, as well...I think it can, especially if the dogs were loaded previously in a more-safe matter.

      It can using require $path_to_breed; instead of eval "use $breed;";. But it ammounts to almost the same thing, except that you have to specifiy the path to the breed file instead of the traditional 'module::name' format. As you say, it's also possible to load all the subclasses before anything is called, but that could get very inefficient fast.

      My favored solution would be to map a breed via a hash:

      my %breeds = ( cocker => 'dog::cocker', setter => 'dog::setter', ); sub new { my $class = shift; my $breed = shift; my $self = { }; if($breed) { my $breed_class = $breeds{$breed}; eval " use $breed_class "; die $@ if $@; $self = $breed_class->new(); } else { bless $self, $class; } return $self; }

      ----
      : () { :|:& };:

      Note: All code is untested, unless otherwise stated

        As you say, it's also possible to load all the subclasses before anything is called, but that could get very inefficient fast.
        Understood. It could be a killer. As a non-perl example, Sun did something like this with java import in 1.4.x, and we saw numerous performance issues -- and we ended up reimplementing lazy loading of some plugins with reflection (i.e. equivalent of symbol tables) -- ugly!
        My favored solution would be to map a breed via a hash:
        Great idea, chief! I'd probably use both the hash and require for purity's sake -- and call the import explicitly. You might (I'm theorizing) save a little performance from not using eval at all in that case. But yeah, then you would lose the exception catching (eval block)???
Re: •Re: It's a dog, but what kind? (polymorphism , in Perl OO)
by rinceWind (Monsignor) on Mar 24, 2004 at 16:12 UTC
    As you point out, using a constructor on an existing object is something unnatural; I agree that ref($proto) should be avoided in normal cases.

    However, there are times when you specifically want to clone or template a new object (e.g. Class::Classless). I you are doing something like this, you should make it clear in the POD that this method call is available, and precisely what it does. This should state (if this is the case) that new can be use both as a class method call and an object method call.

    Also, as part of the POD, it forms part of your interface contract. You should write tests which exercise all the options and prove that your code matches what the POD expects to happen.

    My $0.02

    --
    I'm Not Just Another Perl Hacker

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://339158]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (10)
As of 2014-12-18 22:45 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    Is guessing a good strategy for surviving in the IT business?





    Results (67 votes), past polls