Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

(Re:)+ $class = ref $class || $class

by rir (Vicar)
on Mar 03, 2003 at 21:35 UTC ( [id://240164]=note: print w/replies, xml ) Need Help??


in reply to Re: (Re:)+ $class = ref $class || $class
in thread Constructor/Factory Orthodoxy

If I were faced with that range of classes for $request, I would have to dredge through the various packages to discern what variations in behavior were occurring to figure out just where to patch finish().

Exactly! But you would not have to dredge if the author had written Packet_Foreign->new. This is the root of the objection.

(ref $obj)->new
The code tells that you need to determine the type of $obj to figure out how the call to new is resolved.

Obj->new
The code tells you where to start to resolve the call to new. This can be of great benefit to the reader in some situations.

$obj->new
The code obscures the author's intent. Could he have written Obj->new but didn't just because of mindless imitation? Could he have written (ref $obj)->new but didn't just because of mindless imitation?

I give you rir's observation: When the code is bad, the documentation is worse. So I find your statements to rely on the documentation unconvincing. If truth and beauty don't exist in the code, I won't count on finding them in the documentation.

I am sorry that I unable to make my point to you. Last fall when I first read merlin's criticism of this idiom as Cargo Cultism, I was resistent. My first thoughts: it is no big deal; it's just a little convenience; it is in the Camel; it has never bothered me; and merlyn is being exceedingly fussy.

But since I really had adopted the usage without critical thought. And since I had, still have, no good argument against high standards, especially where it resolves to an idiom, i.e. think once -- type many. I did consider and quickly found fault with the idiom. My reason to deprecate the idiom is different from merlyn's and, I think, a much stronger one.

I won't write $class = ref $class || $class because it supports the thoughtless use of $obj->new which can be obscure.

There are other views: tilly's posts A Cat's eye view of OO and Re (tilly) 2: Paradigm Shift - Dual Use Constructors and their associated threads. Also Re: (Ovid - minor code nits) Re: Adding autoloaded methods to symbol table with using strict refs.

Update: True confession time: When first I adopted the dual method trick its cuteness appealed to me. So I used it unnecessarily.
Fixed minor typos.

Replies are listed 'Best First'.
Re: (Re:)+ $class = ref $class || $class
by herveus (Prior) on Mar 04, 2003 at 12:49 UTC
    Howdy!

    ...but writing Packet_Foreign->new means that I have a priori knowledge that I always need a Packet_Foreign thingy. That is not the situation in the scenario at hand.

    In the scenario under consideration, one is handed an object and needs another of the same class. Either one takes ref($obj) before calling new or one does it *in* new. The scenario does not admit a choice. One *could* pass the value to new as an argument, but that is just another variation (that may be productive) that I'm going to gloss over for now.

    The code obscures the author's intent. Could he have written Obj->new but didn't just because of mindless imitation? Could he have written (ref $obj)->new but didn't just because of mindless imitation?

    I give you rir's observation: When the code is bad, the documentation is worse. So I find your statements to rely on the documentation unconvincing. If truth and beauty don't exist in the code, I won't count on finding them in the documentation.

    It is not reasonable to attribute use of the offending idiom to "mindless imitation". That steers the discourse toward a discussion of the discussor and away from the subject. The followup observation continues that unpleasant course.

    I do not blindly accept that the idiom is, per se, a Bad Thing(tm). I do accept that it has the potential to create confusion. I do claim that proper documentation and the use of it by the programmer should mitigate that confusion. It is not that you are "unable to make your point to [me]"; it is that I do not blindly accept it as the One Truth.

    More generally, what do you expect to happen if you call a class method as an object method? As developer of the module? As user of the module?

    I see this whole issue as having a number of shades of gray, not just black and white.

    yours,
    Michael

      Update: Removed unfounded, baseless slur on tye. My apologies to tye. I was thinking of a post textually near his, I would have rechecked the reference except for the server & network problems mentioned in my post.
      I won't mention the author I meant to refer to because, on review, he didn't not really confess, he just strongly implied that he had rather thoughtless imitated this construct. I am unique, I am the only known confessed and repentant former thoughtless user of $class = ref $class || $class

      writing Packet_Foreign->new means that I have a priori knowledge that I always need a Packet_Foreign thingy. That is not the situation in the scenario at hand.

      That was the scenario with the code I posted. We seem to keep referring to different scenarios and aspects of the issue. I am focused on the unclear use of $obj->new in place of Obj->new and that that muddies the meaning of $obj->new in most situations. You seem to point to $obj->new as an equivalent to (ref $obj)-new.

      All I've got out of your arguments is that you wish to save five keystrokes per call, ()ref, and spend more text in the documentation of the code instead. That seems ridiculous.

      It is not reasonable to attribute use of the offending idiom to "mindless imitation".

      It is reasonable to consider it as a possibility. I am of fair intelligence and have admitted to repeatedly doing so myself. If you followed the links I gave, you will see others confessing also. tye comes to mind offhand. Copying the practices given by Wall, Christiansen and Schwartz is not a bad approach to learning Perl, but it is rote learning and so the label mindless imitation may apply. The writer of $obj->new does not see any reason to distinguish his code from the code of Cargo Cultists, so the reader won't know if it is Cargo Cult Code without doing extra work. The writer of ref $class || $class abets this practice.

      You may have specific intent regarding the meaning of $obj->new but the poor use of the idiom means I have to be clued in by you to know your specific usage pattern and then track who is writing a piece of code. Given that you, and I, are doing so poorly in making our positions clear to one another just reinforces my feeling that the code should be as clear and informative as possible.

      In the absence of compelling reasons I'll choose to avoid creating confusion over creating it and then attempting to mitigate the confusion.

      I see colors and shades of grey also. Here I find no blending of shades. I have given a clear reason to habitually avoid $obj->new. You have just stated or referenced personal preferences, I don't find a reason for your preference in your posts.

      Here in two lines of code the problem can be seen.

      $obj->new; # Perhaps Obj->new could have been written $obj->method; # method is wrong, what class do # we start looking in to fix it?

      More generally, what do you expect to happen if you call a class method as an object method?

      This very discussion demonstrates that this is not a valid question for Perl. Perl has functions and methods. I think of methods being called with a first argument that specifies the class of the method and may have other uses in the method. Perl makes no meaningful distinction between class and instance methods, it is just a bit of syntax. (ref $obj)->routine( $obj) and $obj->routine contain pass the same info.

      As developer of the module?

      In this discussion I have been more in the role of maintainer/evolutionary expander of the client code using the classes containing the new.

      As user of the module?

      I hope that all the arguments are checked for validity as possible. The first argument of method just happens to set on the other side of a method's name.

      I don't see your questions as relevant to the issue at hand. For me it is about clarity, as in locality of information, not about following the object-orientation style of Smalltalk or Self or C++ or ...

      It is interesting that our minds find differing aspects of this discussion as being worthy of expansion. My thoughts run toward variations of
      How to treat comments when reading or reviewing code?

      Sorry -- Would have got back sooner but for network and server problems.

        It is not reasonable to attribute use of the offending idiom to "mindless imitation".
        I [...] have admitted to repeatedly doing so myself. [...] you will see others confessing also. tye comes to mind offhand.
        Sorry, wrong.

        I've scanned all of the threads you linked to and have to assume that you have grossly misrepresented what I said in (tye)Re: You Say "Cargo Cult" like it's a BAD thing!:

        I've come to hate the term "cargo cult" myself. Many people copied that constructor code from standard documentation. I thought such copying was more accurately called "best practices".
        You'll note the conspicuous lack of "mindless" or any synonyms thereof. If I thought the copying in question was "mindless", would I have described it as "best practices"?

        I copied that because I understood it and liked it. There was nothing mindless about it.

        The most direct alternative is to have $obj->new() be interpretted as "$obj"->new() or something like     "Class::Next=HASH(0xdeadbeef)"->new() which is just ridiculous! I wonder how many of your classes have this problem.

        A more reasonable alternative would be:

        sub new { my $class= shift(@_); Carp::croak "Usage: ",__PACKAGE__,"->new( ... )" if ref $class; # ... }
        but I haven't seen anyone advocating that yet. It is very Perlish to DWIM in such a case. In the face of the options available to handle this problem, I support the standard documentation presenting the simple DWIM work-around that leads to reasonable behavior in more cases. It is a very simple approach and it is more "robust" rather than "strict". IMO, it is the best choice (by far) for introductory writings on Perl OO.

        In a much more advanced treatment of Perl OO, I'd expect much more discussion of the problems with that and the reasonable alternatives to it. I have covered two alternatives above (though I only consider one to be reasonable). If I didn't want to allow $obj->new() then I would separate my class methods from my object methods by putting the class methods in one package and having constructors create objects blessed into a different package that contains only the object methods. But that creates quite a few other complications, especially if you want to "support" willy-nilly inheritance which the beginning Perl OO documents appear to want you to do.

        It is rather funny how much ire is directed at that one line. It brings out wails about 'cloning' and yet no cloning is done in that line. You should be complaining about some other line of code that actually copies data from the old object to the new.

        And, since I've read and scanned lots of notes on this topic and don't recall seeing an example of what to replace that line with, I assume that you want me to avoid this type of example:

        sub Foo::new { my $class= shift(@_); return bless [@_], ref($class)||$class; }
        in favor of this:
        sub Bar::new { my $class= shift(@_); return bless [@_], $class; }
        so that my users can see this:
        my $obj= Bar->new(1,2,3); my $other= $obj->new(4,5,6); $other->twirl(); Can't locate object method "twirl" via package "Bar=ARRAY(0xbadd0g +5)"
        and have probably no clue what the heck went wrong.

        There must be some examples of what alternative I should use somewhere in one of these long threads. Someone find one so we can see it. It is certainly rare that such gets shown.

        But I choose to use the oft-hated idiom for more than just that reason. I find that when writing a Perl module for public consumption, there are usually options that I want to give to potential users of my module. I want to support my module being used in more than one place. I want to support each place that uses my module to select the options that make the most sense in the situation at hand. I expect the people who use my module to often want to deal with more than one object at once. I think modules should have unique names and so they tend to have rather long names. So I like to let the module user just mention the module name once (in a use statement) and get a customizable factory object put into a lexical variable with a name that suits their needs.

        In fact, I had to go back and make Win32::TieRegistry->new() even work because I found that people expected that to work despite it never being mentioned in the documentation for that module.

        So instead of having to write, over and over:

        my $key= Win32::TieRegistry->new( "NameOfRegistryKeyToOpen", { Delimiter=>"/", ArrayValues=>1, # Lots of other options }, );
        I let you just do:
        my $key= $Registry->new( "NameOfRegistryKeyToOpen" );
        Now, whether that is "clone" or "new" doesn't much matter to me. [ In fact, you can use $reg->open("key") to get the same thing. I prefer open() but support new() because of the expectations of Perl programmers. ] But the design of new()/open() that the key name is required so that it doesn't really make much sense as a "clone" method since it will be opening a new key. Sure, it copies the options from the object being used as a factory into the newly created object, but I would never name such an operation "clone" ("Here is my clone.", "Um, why doesn't he look just like you?"). You could call it "copy" but that isn't a great fit either.

        I can see where you might not like "new" because you could read it as "shiny new, having no baggage from anywhere else". Well, I say get over that interpretation if you are doing Perl OO. You see all of the grief it is causing you. In my case, use open() instead. But if you think of "new" as meaning "create an object", then you can use that.

        So, you see, in Perl OO, there is no distinction between class methods and object methods (they are both just subroutines in the same package) so I like to make my Perl objects have the ability to be both objects and factories. It is nice to be able to create a not-fully-initialized object for when it is convenient to set some aspect of the object before you set others. So it is only natural to be able to use those as factories as well.

        Sure, in some other OO language I would probably have a separate factory class and would prevent you from creating partially initialized objects so that I wouldn't have to make each method check "is this one ready yet". That is often a very good idea. It is like strict.pm, being less convenient and more strict in hopes of catching mistakes earlier.

        Note, there are things about the latest Win32::TieRegistry that I don't like. For example, exporting the factory object was a bad idea and I haven't quite worked around that early mistake as much as I'd like to yet.

        But I very much like my ref($class)||$class and I copied it from the standard documentation after reading about it, understanding it, and consciously deciding to use it. I understand it even better now and like it even more.

        And it is the best technique for new Perl OO programmers to use in order to prevent confusion. So I even like it when it is mindlessly copied (though I'd rather people not program mindlessly, but I don't always get to control that).

        Some other alternatives (which I haven't noticed the haters-of-this-idiom putting forth) are better for more long-term, big-project, careful, strict coding projects. Such projects should have careful coding standards and not be mindlessly copying stuff out of beginner manuals. If this is a problem that you run into then I suggest you get some coding standards for your project rather than trying to enforce coding standards on the rest of the Perl world (since Perl is such a DWIM/convenience language and Perl OO just doesn't do "strict" very well, IMHO).

                        - tye
        Howdy!

        In the discussion, the point of contention is the use of the idiom ref($proto) || $proto (typically in a constructor), particularly to allow one to call the constructor with an object instead of a class name. Good arguments have been advanced and referred to as to why this practice should be avoided, or at least used with much greater deliberation than would appear typical.

        I am taking as axiomatic that the documentation of the module will adequately cover how one might call the constructor (as well as other methods), including some mention of what one might expect from calling it with an object instead of a package name. I do realize that I am subject to disappointment with that assumption.

        When I fussed about attributing choices to "mindless imitation", I was a bit unclear. The words I responded to seemed to claim that mindless imitation was the only explanation, and I objected to that apparent generalization. I make no claim to being free of that fault, myself. I think I have gotten better at recognizing it in myself.

        When I query about the calling class methods as object methods, I fully realize that the distinction is not inherent in Perl. However, if I have a subroutine in a package that expects an object that isa(__PACKAGE__) as its first argument, such a subroutine has the appearance of being an object method. If that subroutine expects the first argument to be a package name, then it has the appearance of a class method. If that "class method" uses that package name as data, such as blessing a new object, then it is fair to ask how one would expect that method to behave if that package name were actually an object. If the "class method" makes no use of that first argument, then it makes no difference whether it is called as a "class method" or as an "object method". Returning to my axiom, I expect the documentation to help me out here.

        Now, if the documentation says "don't you dare call the class methods with an object, or bad things will happen", you, the user, Have Been Warned.

        On a more concrete level, if I am faced with $obj->method, and I need to discern which instance of "method" is being called, I'm going to be in for some work, no matter what. If there are multiple modules that might have "sub method" in them, whether I say ref($obj)->method or not will not affect the order of magnitude of the search.

        A nitpick: (ref $foo)->method($foo) is not quite the same as $foo->method; the first passes two arguments -- the second but one. The additional argument in the first form does not provide additional information, but it is not quite correct to say that they "pass the same info".

        I suppose that part of my perspective is that I don't have any particular problem with the concept of calling "class methods" with objects. I have not yet come around to the viewpoint that overloading that particular semantic facet is necessarily a Bad Thing. As I noted earlier, we may simply have to agree to disagree on this point. I certainly may be no more than a stubborn git. I cannot deny the possibility.

        yours,
        Michael

        Here here! It's a pity a post can only receive one ++ per person. You have explicated the objections I have to this practice much better than I have.

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

        Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

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

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others surveying the Monastery: (4)
As of 2024-04-19 03:01 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found