Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Classes Are Killing Me

by straywalrus (Friar)
on May 19, 2002 at 04:23 UTC ( [id://167606]=perlquestion: print w/replies, xml ) Need Help??

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

I have used Perl for a while now, but I have never even thought about using it's class capabillites. Now I am and I am dying. The code in "Perl Cookbook" didn't work, so I wrote some of my own. I was able to get some limited functionality, but it seems ridiculous compared with some of the other classes I have seen. here is my module:
package Person; require 5.004; #use strict; my $Person; $Person::version = "1.00.01"; $Person::build = "\$ID:2255518"; $Person::list = {}; sub new { my $class = shift; return bless($Person::list,shift); } sub name { $Person::list->{NAME} = $_[0]; return $Person::list->{NAME}; } sub age { $Person::list->{AGE} = shift if @_; return $Person::list->{AGE}; } sub exclaim { printf "I'm %s and i'm %d years old!\n",$Person::list->{NAME},$Per +son::list{AGE}; return 1; } 1;

And here is my test program:
#!/usr/bin/perl use Person qw/name age exclaim/; my $foo = Person->new(); $foo->Person::name("Stefan"); $foo->Person::name(20); $foo->Person::exclaim();

Any one care to rip this apart and show me really and truly how to do classes in Perl? I would like to be able to call class functions without the "Person:: in front of it. I was looking at the AUTOLOAD in Perl Cookbook, but I wanted to see what the monks would come up with.Any help is appreciated. Thanx straywalrus

Replies are listed 'Best First'.
Re: Classes Are Killing Me
by chromatic (Archbishop) on May 19, 2002 at 04:40 UTC
    Use the $class you're shifting off in your constructor. That's what bless uses to associate a data structure with a class name.
    sub new { my $class = shift; bless({}, $class); }
    I use an anonymous hash there so you're not recreating the same object each time -- if you use $Person::list, you'll always get back the same object. That may be what you want, if you know what a Singleton is, but get this part down first and add complexity later. :)

    Oh, and in your accessors, shift off $self and use that. Have you read perltootc?

      I do know what a singleton is, but thanx for that pointer. I got the idea to use the local copy of I had originally just copied the data used in the users junk to the module. but even when i used this constructor:
      sub new { my $class = shift; my $self = {}; return bless $self, $class; }
      It actually did not work at all. Besides, the second simplest constructor in the perl cookbook was sub new{ bless({},shift);} I saw that in the reference they had shifted off of $self for every function, but as I said earlier I was just copying the code to local vars and only now realize that I can only have one instance of the class if I do that. Yes I realize how to shift of $self, but even when I had all the things that were just descibed, I still could not call the functions like this:$foo->name("straywalrus");. Instead I had to call every function with the class name preceding the function name with the C++ scope operators betwixt the two ( I have no idea what the scope "::" operator is called in Perl). Every call was made like it was shown in the test program. Surely there is a way to correct this, even though I shift off $self and do as was above described. Thanx straywalrus

        The simplest remaining possibility is that $foo is not a person, that your constructor is somehow failing. This could be due to the package not loading. If I were you, I'd enable strictures and warnings and add this line after attempting to create the object:

        print "Foo is a ", ref($foo), "!\n";

        You shouldn't have to preface method calls with package names, nor do you need to import anything from Person (so you can safely drop the method name list). Oh, and the double colons are package name separators.

Re: Classes Are Killing Me
by ariels (Curate) on May 19, 2002 at 06:27 UTC

    Always turn on warnings (<samp>perl -w</samp> or <samp>use warnings</samp> in newer Perls); for that matter, use strict unless you have good reason not to. In your case, -w would have caught the bug in your constructor:

    sub new { my $class = shift; return bless($Person::list,shift); }
    doesn't use $class, instead preferring to shift some other element off of @_ to use as the class name. Additionally, you're always blessing and returning the same object (needlessly stored in a global variable), which is probably not what you wanted to do. In your case, you're passing undef to bless; you want to say
    sub new { my $class = shift; return bless({},$class); }
    Continuing down your code, your methods aren't reading the object they should be working on. You want to say e.g.
    sub name { my $self = shift; $self->{NAME} = shift; }
    Do this, and you should be able to say e.g.
    my $person = new Person; $person->name('Me');

    I'm curious, though, why you say ``the code in "Perl Cookbook" didn't work''. Could you please point us to what isn't working there?

      I covered the fact that I was only able to create one instance of this class without losing data associated with it in my first comment to Chromatic(the comment is two above yours). However thank you for reiterating such a point. The code in perl cookbook would not even create an object or (after modification) it would not find the functions in package main.Of course this was shifting off of $class and even then they would not find the functions. If you really want the error returns, I could retype those programs and start a fresh to generate the errors. And in fact, the second most simple constructor is bless({},shift); I ussually use strict but i did not here, but if you noticed i made it very simple to add use strict: most variables are preceded by my. And this was just an example that I was trying to get to work. If i really wanted to I could (and do for my other projects) use strict
        You have a common problem. You had a small mistake, you had a wrong idea about where the mistake was, came up with a theory, and from there you have careened to a number of erroneous conclusions. And now those conclusions leave you unable to understand or accept good advice when you get it.

        ariels is right. You bless $Person::list into no class at all. This blesses it into the empty class, which is the root of the entire tree of package namespaces, and is better known as main. This keeps you from calling methods normally, so you get around this by directly specifying the package to start the method search in. And somehow you convinced yourself that the object didn't have private data like the books says it should, so you started using globals. (Probably you just wrote an accessor wrong somewhere, and had a wrong conclusion.)

        Please accept that everything you think you know is probably wrong. It will make learning the truth much easier. Here is some of that truth.

        When a function receives a method call, @_ holds the callee and then the arguments to the call. The callee is what the method was called on, which can be a class (ie package name) or an object (ie blessed reference). Normally only constructors get package names, and they are supposed to construct an object and bless them into that package. A method call looks like $object->bar(@stuff) (with the function getting an @_ that looks like ($object, @stuff)).

        Now let's rewrite your code properly. First your module:

        package Person; use strict; sub new { my $class = shift; my $self = {}; return bless($self, $class); } sub name { my $self = shift; $self->{NAME} = shift if @_; return $self->{AGE}; } sub age { my $self = shift; $self->{AGE} = shift if @_; return $self->{AGE}; } sub exclaim { my $self = shift; printf "I'm %s and i'm %d years old!\n",$self->{NAME},$self->{AGE} +; } 1; # Modules need to end in a true value
        And the driver code.
        #!/usr/bin/perl use strict; use Person; my $foo = Person->new(); $foo->name("Stefan"); $foo->age(20); my $bar = Person->new(); $bar->name("Barney"); $bar->age(3); $bar->exclaim(); $foo->exclaim();
        Yes, the constructor that you thought didn't work, does. You typed it in wrong, blamed the wrong thing when strange stuff started to happen, and went further and further wrong from there. Points to take away.
        1. Be very hesitant to say that a well-regarded book is wrong. It is more likely that it is right and you merely misunderstood it. Try typing in exactly what it has.
        2. When you get advice back on elementary topics, try it before saying how it will or will not work.
        3. Before ever saying that things don't work like they are supposed to work, look for elementary mistakes. Like typos, or mixing code from 2 functions and getting a result that does something unexpected. (Like bless an object into the wrong package.)
        4. Use strict. It takes less work to use it than explain why you didn't. :-)
Re: Classes Are Killing Me
by meta4 (Monk) on May 19, 2002 at 22:39 UTC

    If I'm reading you right you want your main routine to look something like this:

    use person; my $foo = Person->new(); $foo->name("Stefan"); $foo->age(20); $foo->exclaim();
    and you also want to be able to do something like this,
    $bar = Person->new(); $bar->name("Yoda"); $bar->age(900); $bar->exclaim();
    in the same program.

    I think the conceptual gap you are having is, an object in perl is really just a reference. (In this case a reference to a hash.) Perl uses some syntactic sugar that makes object-oriented programing easier. (Confusing for the beginner, but more powerful for the wizard) When you use object-oriented syntax to make a “method call” (a function call via an object) using code that looks like this:

    $foo->name("Stefan");
    it is really the same as making a regular function call like this
    Person::name( $foo , "Stefan” );
    That is, if $foo is a reference that has been blessed into to a Person object.

    You see, the blessed reference “knows” what package it belongs to. So the ‘Person::’ gets tacked on the front of the function call. Also, the reference is used as the first argument to the function. That’s why all the method functions shift off a reference from the argument list.

    This method of shifting off implied references may seem strange but it is the key to having multiple objects from the same class. Consider the following:

    my $ref1 = { “name” => “Stefan”, “age” => 20 }; my $ref2 = { “name” => “Yoda”, “age” => 900 };
    These are obviously two separate anonymous hashes. Now if we were to do the following (preferably in new or any constructor subroutine)
    $ref1 = bless $ref1 “Person”; $ref2 = bless $ref2 “Person”;
    we would have two completely separate Person objects.

    The bless function takes a reference as the first argument, and a class name as the second argument. All it does is allow you to call functions that are in a package that is specified when the reference is blessed, but the package does not need to be specified when the function is called.

    With this in mind, see if you understand the following. I tried to make it do what I though you wanted Person to do

    package Person; require 5.004; #use strict; my $Person; # These Variables are Static variables. # All Person objects share these variables. $Person::version = "1.00.01"; $Person::build = "\$ID:2255518"; # this only lets you create one person. I don't think you # whan to do that. # $Person::list = {}; sub new { my $class = shift; # $person is a blessed reference to an anonymous hash my $Person = {}; my $Person = bless $Person, $class; return $Person; } sub name { # Get the reference to the hash that represents this person $Person = shift; # Set the name member of the anonymous hash to the argument # of this function. $Person->{NAME} = shift; return $Person->{NAME}; } sub age { # Get the reference to the hash that represents this person $Person = shift; # set the age member of the anonymous hash to the argument $Person->{AGE} = shift if @_; return $Person->{AGE}; } sub exclaim { # Get the reference to the hash that represents this person $Person = shift; printf "I'm %s and i'm %d years old!\n", $Person->{NAME},$Person->{AGE}; return 1; } return 1;
    The above code works when called as follows. I kept your code in comments to correlate with your original question.
    #!/usr/bin/perl # use Person qw/name age exclaim/; use person; my $foo = Person->new(); # $foo->Person::name("Stefan"); $foo->name("Stefan"); # $foo->Person::name(20); $foo->age(20); # $foo->Person::exclaim(); $foo->exclaim(); $bar = Person->new(); $ bar->name("Yoda"); $ bar->age(900); $ bar->exclaim(); # See, it's still here $foo->exclaim();

    I’m Sure the above could be optimized quite a bit. But I tried to make one step per line for educational purposes.

    For more information I really recommend you read perltoot it should also be included with the perl documentation on your system. It contains far more than you ever wanted to know about objects in perl. I believe it also includes examples.

    As an exercise see if you can modify new so that it can be called as follows

    my $foo->new( “Stephan”, 20 ); $foo->exclaim(); my $bar->new( “Yoda”, 900 ); $bar->exclaim();

    Can you tell I just saw Star Wars?

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others cooling their heels in the Monastery: (4)
As of 2024-04-23 07:15 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found