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

Disclaimer
The question is at the end of the post.

THE BACKGROUND

I am writing a module with a funtional and OO interface a la CGI.pm. There is one function that is exported by request. This function prints data to STDOUT. For example,

use Some::Module qw(the-mascot); the-mascot();
There are 2 methods, animal and mascot. Like the exported function, the method called mascot also prints to STDOUT. Here's another example:
use Some::Module; my $a = new Some::Module; $a->animal("parrot"); $a->mascot;
Now keeping all of this in mind let me show you some code

THE CODE

This is my code. I know that it does not work. The purpose of this code will become apparent in my question. This is pseudo code. So if you see a typo or don't see strict or warnings, please don't freak out.

$data = "camel"; sub the-mascot { print<<"END"; Hey, did you know that Perl's mascot is a: $data END } sub new { my $class = shift; my $self = {}; $self->{ANIMAL} = $data; bless($self, $class); return $self; } sub animal { my $self = shift; $self->{ANIMAL} = $_[0]; } sub mascot { the-mascot(); }

THE QUESTION

Notice that I have a global variable called $data. This variable has 2 purposes. First, $data is used in the heredoc in the the-mascot function. Second, $data is the default value for $self->{ANIMAL} in the new subroutine. I want to eliminate the use of this global variable.

Instead of using $data in the heredoc, I want to use the current value of $self->{ANIMAL}. Remember that this value gets set in the new subroutine. But it SHOULD change if the user calls the animal method in the main package, as I did in the previous example. So when someone says $a->animal("parrot"), then $a->mascot should print:

Hey, did you know that Perl's mascot is a: parrot

Now before you reply, remember that this would not work:

sub the-mascot { my $self = shift; print <<"END"; Hey, did you know that Perl's mascot is a: $self->{ANIMAL} END }
This doesn't work. Remember that the-mascot is a function exported by request. It is not a method! So I would like to set the value for $self->{ANIMAL} in the new subroutine and then reference that value in other subroutines which are not methods and do not care if the constructor is ever used in main.

How could I accomplish this and eliminate the use of the global variable? Thanks.

Replies are listed 'Best First'.
Re: Getting an "object's" value before it becomes an object
by davido (Cardinal) on Jan 22, 2005 at 05:52 UTC

    The problem is that an object, by definition, encapsulates its data. And yet you wish to externalize some portion of the object's encapsulated data so that it's available to any subs in the package, inaddition to object methods. That's messy. Furthermore, an object is usually "an" instance of a class, not "the" instance of a class. That means you can have several object instances of a class, each with its own separate object data. If there is to be a piece of class data that is the same for all object instances, your use of the global variable is the cleanest solution I can think of, so why jump through hoops to figure out a way to eliminate it?

    An object's data isn't intended to be available before the object is created, and yet still unique to the object. You may be able to come up with some contortion using an inside-out object where objects are clones of some parent datastructure, but that's no better than what you've got now, and definately harder to maintain.

    I guess my question is why is it so important that you eliminate a package global when it's doing what you want? And would it suffice to create a package-scoped lexical? By creating an outter lexical block that is as broad as the package, and declaring a lexical (my) variable within it, you'll have a variable that looks global to the rest of the package (except there's no entry in the symbol table), but is inaccessible from outside the package (because there's no entry in the symbol table -- assuming that's your goal).


    Dave

      I guess my question is why is it so important that you eliminate a package global when it's doing what you want?

      Suppose there are 20 methods each of which is associated with a unique message. Then I would need 20 such variables to go along with them. But your point is well taken.

Re: Using an object's value instead of a global variable
by BrowserUk (Patriarch) on Jan 22, 2005 at 06:01 UTC

    In OO terms your exported sub the_mascot() ('-' isn't valid in a perl indenntifier :), is a class method.

    However, you want this class method to return instance data. And that is a non-sequitous requirement.

    Unless this is a singleton class, which is a fancy term for global, in which case you could store the handle ($self) to the one instance into $data and then use $data->{ANIMAL} in the here-doc and you would achieve what you want.

    Then you would have to arrange to update $data with the object handle of the lastest incarnation of the singleton everytime new was called.

    That's not to say it is the correct way, or the best way to do it, but it would work.

    The problem is, if there can be more than one instance of your class, which value of ??->{ANIMAL}, ie. from which instance, should your exported function use?


    Examine what is said, not who speaks.
    Silence betokens consent.
    Love the truth but pardon error.
      Unless this is a singleton class, which is a fancy term for global, in which case you could store the handle ($self) to the one instance into $data and then use $data->{ANIMAL} in the here-doc and you would achieve what you want.

      Okay, you've got me interested but I'm a bit lost. Could you kindly show me how?

        I do not think that this is a good idea but:

        The module (with a stupid name for my purposes):

        package _424188; require Exporter; our @ISA = qw[ Exporter ]; our @EXPORT = qw[ the_mascot ]; my $data = { ANIMAL => "camel" }; sub the_mascot { print<<"END"; Hey, did you know that Perl\'s mascot is a: $data->{ANIMAL} END } sub new { my $class = shift; return bless $data, $class; } sub animal { my $self = shift; $self->{ANIMAL} = $_[0]; } sub mascot { the_mascot(); } 1;

        And a program that uses it:

        #! perl -slw use strict; use _424188; the_mascot(); my $obj = new _424188; $obj->mascot; $obj->animal( 'dromadary' ); $obj->mascot; the_mascot(); __END__ P:\test>424188 Hey, did you know that Perl's mascot is a: camel Hey, did you know that Perl's mascot is a: camel Hey, did you know that Perl's mascot is a: dromadary Hey, did you know that Perl's mascot is a: dromadary

        Examine what is said, not who speaks.
        Silence betokens consent.
        Love the truth but pardon error.
Re: Using an object's value instead of a global variable
by johnnywang (Priest) on Jan 22, 2005 at 07:02 UTC
    The others have already pointed out why that's not a good idea. But to show it's still possible, I assume the following is close to what you want:
    package Bar; use strict; use Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(the_mascot); my $value = "camel"; my $data = \$value; sub the_mascot { print<<"END"; Hey, did you know that Perl's mascot is a: $$data END } sub new { my $class = shift; my $self = {}; $self->{ANIMAL} = $data; bless($self, $class); return $self; } sub animal { my $self = shift; ${$self->{ANIMAL}} = $_[0]; } sub mascot { the_mascot(); } 1;
    Then in main script:
    use strict; use Bar qw(the_mascot); my $a = new Bar; $a->animal("parrot"); $a->mascot; the_mascot(); $a->animal("firebird"); $a->mascot(); #output __END__ Hey, did you know that Perl's mascot is a: parrot Hey, did you know that Perl's mascot is a: parrot Hey, did you know that Perl's mascot is a: firebird
    </code>

      What you have proven is that it's possible to declare a lexical at file-scope that works a lot like a package global (but not exactly), but without generating an entry in the package global symbol table. Thus, this file-scoped lexical is not available outside the package (ie, via some fully qualified name, nor outside the enclosing file).

      That doesn't show anyone how to cause an object's instance data to be available prior to object creation time, to a package bound sub (or a class method), which seems to be the question.


      Dave

Re: Using an object's value instead of a global variable
by Errto (Vicar) on Jan 22, 2005 at 17:13 UTC
    This might do what you want. It allows you to create multiple instances, and it allows the mascot method to print the value of that instance. You can also call animal as either a straight sub or a method, and either way it modifies the value that will be printed if you later call the_mascot as a straight sub. This is different from what you described, but what you described left quite a lot of ambiguity about whether it's ever legal to create more than one instance of this class, as others have mentioned. Like johnnywang I use a file-scope lexical instead of a package global.
    package Mascot; use strict; use Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(the_mascot); my $value = "camel"; sub the_mascot { my $data = shift || $value; print<<"END"; Hey, did you know that Perl's mascot is a: $data END } sub new { my $class = shift; my $self = {}; $self->{ANIMAL} = $data; bless($self, $class); return $self; } sub animal { my $self = shift; my $newval; if (ref $self eq __PACKAGE__) { $self->{ANIMAL} = $newval = shift; } else { $newval = $self; } $value = $newval; } sub mascot { my $self = shift; the_mascot $self->{ANIMAL}; }