http://qs1969.pair.com?node_id=114769

fx has asked for the wisdom of the Perl Monks concerning the following question:

Hi there,

I've been working through Learning Perl and perldoc perltoot and I can't find an answer to my query.

I have got a simple class with a simple 'new' constructor which sets up an anonymous hash and sets up some atrributes:

$self->{NAME} = ''; $self->{USERNAME} = ''; ... etc ...

However, I have another constructor in my class which works in a different way and sets some of the attributes of the object before returning it.

My question: within a class file, how should I call the basic constructor from within my more complex constructor?

Thanks,

fx

Replies are listed 'Best First'.
Re: OO Perl: calling a constructor within a class
by davorg (Chancellor) on Sep 26, 2001 at 14:10 UTC

    Many of my objects have a structure that looks like this:

    package MyObject; sub new { my $thing = shift; my $class = ref $thing || $thing; my $self = bless {}, $class; return $self->init(@_); } sub init { my $self = shift; # do initialisation stuff with the values in @_ return $self; }

    Does that help you at all?

    Update: Actually I tend to agree with both tilly and merlyn on the class/instance method divide. The $class = ref $thing || $thing idiom just seems to be hard-wired into my conciousness thru reading in so many books. I have to think twice to not use it. Generally my new method will be a class method and I'll only create an instance method version in the rare cases when I need a copy constructor.

    --
    <http://www.dave.org.uk>

    "The first rule of Perl club is you don't talk about Perl club."

Re: OO Perl: calling a constructor within a class
by jeroenes (Priest) on Sep 26, 2001 at 14:08 UTC
    The only thing special about a constructor is that it blesses a var. The name 'new' is picked by convention. So no hidden language tricks here.

    Just call them at will, make sure you only bless once. Add a check like:

    sub new { my $me = shift; if (ref( $me ) eq 'my_class_name') { print "I'm blessed!\n"; } else { ......
    Hope this helps,
    Jeroen
    "We are not alone"(FZ)

      Using code like ref($me) eq 'my_class_name' can break inheritance if $me doesn't contain an object of your class, but rather an object of a class that inherits from your class. Far better to use the isa method (from the UNIVERSAL class).

      if (ref($me} && $me->isa('my_class_name')) {
      --
      <http://www.dave.org.uk>

      "The first rule of Perl club is you don't talk about Perl club."

        In general using this code can cause an error, unless you can be sure that if $me is a reference that it is a blessed reference.

        It is this that led to people doing

        if(ref($me) && UNIVERSAL::isa($me,'my_class_name')) {

        which is ugly, IMO.

        The Scalar::Util package, that as of 5.7.2 is part of the perl distribution, contains a sub blessed() to get around this

        if(blessed($me) && $me->isa('my_class_name')) {

        As merlyn showed me before, you could alternatively use 'can' to check whether $me is an object or not, and leave it alone when it is, trusting that the class of the object is fine:
        if ( ref $me and $me->can('can') ){ .....
        I did have some discussion with tye about can, and at the time he convinced me to use isa instead: ... $me->isa('UNIVERSAL').

        More importantly, reading perlbot I realised that all that caution about bless is unnecessary. So I rephrase my advise above:

        There is nothing special about constructors other than that they bless a variable. Since you are allowed to rebless a variable, just call the constructors at will.
        In perlbot you can find more info and an example of nested constructors.

        Reblessing assumes that the caller knows what (s)he is doing and that the constructor should always return an object of its own class. This antagonizes the logic behind bless-checking. The reblessing approach is the most flexible one, as constructors do not have to make assumptions about which class the returned objects should be in.

        This in turn made me realise that multiple inheritance could, with a little caution, even apply to the object's data. Just call both constructors and merge the resulting anonymous arrays. Problems will arise when the inherited classes use the same hash-entries for different purposes or when different types of variables are used, of course. At the end, one just reblesses the anonymous hash.

        Jeroen
        "We are not alone"(FZ)

Re (tilly) 1: OO Perl: calling a constructor within a class
by tilly (Archbishop) on Sep 26, 2001 at 16:35 UTC
    I differ in my tastes from davorg. Instead I, like merlyn, prefer to keep instance and class methods separate. His opinion is stated more strongly than mine would be, but I would still make new your basic constructor, give your complex constructor from an existing object a more descriptive name, then just call new from within the more complex constructor. Something like this silly little example:
    sub copy { my $self = shift; my $copy = ref($self)->new(%$self, @_); # Do stuff return $copy; } sub new { my $class = shift; return bless ({ NAME => '', USERNAME => '', # etc @_ # allows overriding defaults in the constructor :-) }, $class); }
      I'm with merlyn -- ref($proto) || $proto is total cargo cult. I've never seen anyone actually use it to call new() as an instance method.
        I actually have seen people do it. But not often enough for it to be worthwhile in most circumstances.

        For the record it does serve a purpose. That purpose is to make Perl's OO look like it is prototyped-based. Depending on what language you first saw OO in, doing otherwise may feel unnatural.

        Before people who don't know what I am talking about go, "Huh?", let me explain.

        Suppose for a second that I had a class named Cat, a subclass named Cat::Siamese, and specific siamese cat named $meows_loudly. At this point you likely have a reasonable picture of what $meows_loudly looks like, and can probably make an educated guess as to what she sounds like. Now where do you get that picture from?

        Well if you are like most people you have a model of what a Cat::Siamese should be like. This model you have is probably based on a few cats you have met in the past. But does this model exist? Well, perhaps. It certainly isn't a walking, breathing cat (the abstract model, not the one sitting in your lap modelling right now) but philosophers can argue endlessly over whether the idea is a real thing in and of itself.

        But hey, what if we are talking about a programming language? Remember that programs don't have walking, breathing, cats. Programs have a different sense of what it means for things to exist. In a programing language we can identify the bits that make up $meowser, and we also have implemented the class Cat::Siamese. Given that the class has an implementation in the computer, it is as real as anything else there, so Cat::Siamese really exists. In fact in any self-respecting OO language (which Perl is not) it is going to be an object. But, to keep the philosophers happy, we get a new question! Namely, is Cat::Siamese a Cat?

        Now in real life there is no question. The idea Cat::Siamese is an idea. We established that it isn't truly a Cat. However computer languages are not the real world, they are human models. And in case you didn't notice, a human model of Cat::Siamese is pretty much always based on some real breathing cat. So to cut off the philosopher's fun, the answer to the question about whether or not Cat::Siamese is a Cat is, "It depends on the language."

        But the philosophers get the last laugh after all. Some people prefer to have an actual prototypical Cat that they call Cat::Siamese, and so like languages where Cat::Siamese is a real cat with methods like meow. The classic language that takes this approach is Self. The alternative approach is to make things like Cat and Cat::Siamese Objects of class Class. Cat::Siamese is not a Cat, and to treat it like one is to indicate fundamental confusion about the nature of the universe. The classic paragon of this approach is Smalltalk.

        Now where does Perl stand? Well it is a functional hodgepodge with no conceptual clarity. In Perl a Class is actually a string that names a package that has stuff implemented in it. There is no way that "Cat::Siamese" is an object inheriting from Cat. But on the other hand if you are going to make $meows_loudly able to meow, then Perl will let people ask Cat::Siamese to meow. It might not work. Certainly I would be inclined to expect that asking Cat::Siamese to meow will run into trouble when you ask how it should meow. (A question I hope will arise, else $meows_loudly is horribly misnamed.)

        So how should a programmer handle it? Well we tend to make things look familiar. People whose first exposure to OO was to a prototype based language like C++ will often try to make Perl work like a prototype based language does. And they can get half-way with the ref($proto)||$proto trick. Now $meows_loudly will serve perfectly well as a substitute for Cat::Siamese (1). People whose model of OO says that classes and instances of a class are unrelated kinds of things see that as a horrible mistake. They prefer to try to ignore the fact that you *can* call class and instance methods on both the cats and the instance, and then make it obvious that something is wrong by letting it break when you do the wrong thing.

        (1) And when you consider the effect of calling new on $meows_loudly, perhaps you see why I called her a "she"? The male doesn't really enter the picture, but that isn't too unusual for cats. The litter is kind of small though...

(tye)Re: OO Perl: calling a constructor within a class
by tye (Sage) on Sep 26, 2001 at 19:53 UTC

    I don't think anyone bothered to simply answer your question (not a criticism of any of the fine replies, just a justification for this one):

    sub another_constructor { my $class= shift; my $self= $class->new(); .... }

            - tye (but my friends call me "Tye")
Re: OO Perl: calling a constructor within a class
by alien_life_form (Pilgrim) on Sep 26, 2001 at 14:51 UTC
    Greetings.
    I do this kind of things thusly:

    package Foo; my %DEFAULTS= ( NAME=>'', USERNAME=>'', ); sub new { my($pkg,%rest)=(@_); if(ref($pkg)) { #called as $instance->new() #do something about this. ; } # Checks, maybe? and other stuff. # this overwrites the stuff in %DEFAULTS with the stuff in # in %rest, if applicable. bless { (%DEFAULTS,%rest) }, $pkg; } ## #Main ## my $foo=Foo->new(); # gets blank NAME, USERNAME my $bar=Foo->new(NAME=>'bar', USERNAME=>'baz'); print STDOUT "Your mileage WILL vary\n";
    Cheers
    alf
Re: OO Perl: calling a constructor within a class
by Anonymous Monk on Sep 28, 2001 at 00:34 UTC
    Yet another solution would be to use Class::Multimethods, and have both constructors named new:
    package MyClass; use Class::Multimethods; multimethod new =>('$') => sub { my ($class) = @_; bless { name => '', rank => '', cereal_number => '', }, $class; } multimethod new =>('$','$','$','$') => sub { my ($class, $name, $rank, $c_num ) = @_; bless { name => $name, rank => $rank, cereal_number => $cnum, }, $class; }
    BTW, there'll be a new version of Class::Multimethods out in the next month or two that lets you define multimethods like they will (probably) be written in Perl 6:
    package MyClass; use Class::Multimethods::Attributes; sub new :multi ($class) { bless { name => '', rank => '', cereal_number => '', }, $class; } sub new :multi ($class, $name, $rank, $c_num) { bless { name => $name, rank => $rank, cereal_number => $cnum, }, $class; }
    Damian
      I'm not sure I like this polymorhic method-naming stuff that'll be happening in the background. Perl is weakly-typed, so why do a work-around that strongly-typed languages have to do? Instead, why not do the following?
      sub new { my ($class, $name, $rank, $c_num) = @_; $name ||= ''; $rank ||= ''; $c_num ||= ''; bless { name => '', rank => '', cereal_number => '', }, $class; }
      That, to me, just feels cleaner and is more maintainable. If you make a change in new(), you only have to change it in one place. Especially if it's in a base class. If it's in a base class and you only change one, you suddenly have weird behavior in the grandchild classes and debugging it would take an age-and-a-half. *shudders*

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

        Careful with that ||=, Eugene!
        Did you really want to prohibit a cereal number of zero?

        That's why I generally avoid using ||= to specify defaults.
        Unless I'm very sure my incoming data won't ever be 0, "0", or "".

        Besides, there are ways to tell if she is a witch:

        $name = '' unless defined $name; $rank = '' unless defined $rank; $c_num = '' unless defined $c_num;
        or:
        foreach ($name, $rank, $c_num) { defined or $_ = '' }
        or the strange-but-lovely:
        $_ = '' for grep !defined => ($name, $rank, $c_num);
        or the tried and true:
        $name = '' if weight($name) == weight($duck); $rank = '' if weight($rank) == weight($duck); $c_num = '' if weight($c_num) == weight($duck);

        Err...

        Oh, yes, I should also mention that next week, when Apocalypse III and Exegesis III appear, you'll find that Perl 6 is going to have a much nicer way to solve this problem.

        Damian

        I agree that for this particular example a workaround is trivial and this module is probably not the best way to go, but I think the general idea of the module is great and can be used in non-trivial ways. And it might be one way to go for the original poster's problem, though (from my limited understanding of Class::Mulitmethods), he would have to do something more different than just passing in a simple scalar string with two different values, he would have to instead have different argument types or different numbers of arguments (Class::Multimethods doesn't seem to be able to dispatch based on the value of scalar args, just their type).