Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

Behavior of 'our' variables in the package-namespace

by Apero (Scribe)
on Nov 02, 2015 at 01:44 UTC ( [id://1146647]=perlquestion: print w/replies, xml ) Need Help??

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

I'm trying to define an OO package that the main:: package will use, and wish to include some common variables that the package will need to reference across all object use (constructors, methods, and so on.)

As a convenience, I'm including the OO package in the same file as the main:: code, ideally at the end as I do not wish to confuse users (read: other staff who don't need to know/care) who may want to learn how the high-level code works or make changes.

Here's where I ran into behavior which I don't really understand. If I put my OO class, called Foo::Bar in the examples below, in the same file at the end, Perl does not let me reference the globally-scoped package namespace variable $Foo::Bar::data within methods of this class. However, if I use the same package code and require or use it from an external library, it seems the non-subroutine (ie: method/constructor/etc) code in the class is run.

I've learned that I can work-around this by using a forward-declaration with our(), combined with a BEGIN block to initialize the package globals. This seems to have the desired result, although I'm wondering if this is really the best way to support using an OO package like this from either a package definition in the same file as main:: or a real module file included with require.

Below is the admittedly contrived proof-of-concept example. Yes, it's a bit silly (my original problem dealt with an OO class that ships its own DBI and SQLite RDBMS, including self-provided schema. Rest assured the example is simply to avoid all of that nonsense in learning how namespace and scoping works!)

Of note here is that removing the BEGIN block surrounding the $data definition prevents the code from ever getting executed, which causes the program to abort during the sanity-check within the text() method.

I'm curious if this is an intentional design limitation of Perl, or merely a side-effect of putting my main:: package code before the Foo::Bar package code. I understand that a require() call will execute the non-subroutine code in the package at that point, but this seems to be in contradiction to how subroutines work; I can call sub_foo() before declaring it, but apparently can't do this with in-file packages.

Here's the proof-of-concept code:

use strict; use warnings; my $foobar_o = Foo::Bar->new( "object o" ); printf "%s\n", $foobar_o->text(1); # -- # Package # -- package Foo::Bar; # Package namespace 'our' globals (which must be forward-declared.) # These go in BEGIN so they work from an in-file package too! our $data; BEGIN { our $data = { 1 => "one", 2 => "two", 100 => "one hundred", }; } # Fairly minimal constructor, passed an optional "name" argument: sub new { my $class = shift; my $name = shift // "<unnamed object>"; $class = ref($class) || $class; # subclass boilerplate. my $self = { name=>$name }; bless $self, $class; return $self; } # Silly example method that returns the "text" keyed by %$data. sub text { my $self = shift; my $query = shift or return ''; # Sanity check, making sure the package namespace $data exists: die "Uh-oh, data is not defined!" unless defined $data; my $s = sprintf( "%s: %s is %s", $self->{name}, $query, $data->{$query} // "<query value not defined>" ); return $s; } # Explicit RC to support require() and use(). # This is only useful when the package is a real module file. 1;

Replies are listed 'Best First'.
Re:Behavior of 'our' variables in the package-namespace
by GrandFather (Saint) on Nov 02, 2015 at 02:24 UTC

    Without the BEGIN your code looks somewhat like:

    my $foobar_o = Foo::Bar->new("object o"); printf "%s\n", $foobar_o->text(1); # some random stuff our $data = { 1 => "one", 2 => "two", 100 => "one hundred", }; # some more random stuff

    from which you can see the initialization of $data doesn't happen until after the call to text. Nothing to do with our versus my. Put the package at the top of the file and educate your users, or stick with the BEGIN. In either case "my" would be better than "our" in the context shown.

    Premature optimization is the root of all job security

      Ah, thanks for the reply! Yes, I realized the definition of $Foo::Bar::data never occurred in time without BEGIN, but I didn't realize that non-subroutine code, even within package definitions was "not special." I'm so used to subs getting declared after they're referenced, but of course the compiler knew about them when the script was processed.

      I probably need to think of packages less as an atomic collection of variables, subroutines, and code, and more as a strict division of namespaces. The code that I'm calling in BEGIN above obviously needs to happen before the method that needs it, and I somehow expected the compiler to magically deal with that. What fun would it be if Perl did all my work for me? ;) I read that in the docs for require() but I guess it didn't make sense until you broke it down a bit more directly.

      The hint about our() was very useful, thanks! I was under the impression that our was needed to get persistent data in a package namespace, but it seems that only allows the variable to be referenced from another class, clearly not required in well-designed OO code where accessors and methods are supposed to hide such details. There's no point in creating true globals across packages needlessly.

      Thanks again for the reply!

Re: Behavior of 'our' variables in the package-namespace
by Anonymous Monk on Nov 02, 2015 at 02:37 UTC

    If I put my OO class, called Foo::Bar in the examples below, in the same file at the end, Perl does not let me reference the globally-scoped package namespace variable $Foo::Bar::data within methods of this class. However, if I use the same package code and require or use it from an external library, it seems the non-subroutine (ie: method/constructor/etc) code in the class is run.

    You need to review what strict vars does for you , see Tutorials

    perl is permissive, perl allows you to do pretty much anything , unless you tell it to, hey, protect me from these common mistakes

    Coping with Scoping and Modern Perl give a good introduction

    I've learned that I can work-around this by using a forward-declaration with our(), combined with a BEGIN block to initialize the package globals. This seems to have the desired result, although I'm wondering if this is really the best way to support using an OO package like this from either a package definition in the same file as main:: or a real module file included with require.

    There is a right way to inline a module, see zentara package/module tutorial

    our is not a "workaround", when you enable strict vars, our is one way to communicate to strict, this is not a typo, this is a variable of mine

    To inline a module you need BEGIN{} because use is executed like BEGIN at compile time

    See Coping with Scoping

    I'm curious if this is an intentional design limitation of Perl, or merely a side-effect of putting my main:: package code before the Foo::Bar package code. I understand that a require() call will execute the non-subroutine code in the package at that point, but this seems to be in contradiction to how subroutines work; I can call sub_foo() before declaring it, but apparently can't do this with in-file packages.

    Neither. Everything including perl has a compilation order and execution order -- this is not a design limitation -- you gotta bake the bread before you can eat it, assemble the bicycle before you can ride...

    You can't access the value of a variable before it is assigned, without a BEGIN{} our $data runs after   $foobar_o->text(1); is called ... too late

    See BeginCheckInitEndUnitcheck.pl

    # Package

    Why do you need a comment to tell you that "package Foo::Bar;" is a package? Think about it:)

      i very like his Anonymousity!! ;=)
      namesté AnonyMonk and thanks for your contributions!!

      L*
      There are no rules, there are no thumbs..
      Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: Behavior of 'our' variables in the package-namespace
by SimonPratt (Friar) on Nov 02, 2015 at 17:27 UTC

    When writing OO classes, try to stay away from initialising globals in the outer class definition. Doing that requires (as you have seen) funky work arounds, BEGIN blocks and the like. Its better to encapsulate your packages more clearly, such as the following:

    use strict; use warnings; my $foobar_o = Foo::Bar->new( name => "object o" ); printf "%s\n", $foobar_o->text(1); # -- # Package # -- package Foo::Bar; # Package namespace 'our' globals (which must be forward-declared.) our $data; # Fairly minimal constructor, passed an optional "name" argument: sub new { my $class = shift; $class = ref($class) || $class; # subclass boilerplate. my %params = @_; initialise(); my $self = bless {}, $class; foreach my $key (keys %params) { my $func = lc $key; next unless $self->can("$func"); $self->$func($params{$key}); } return $self; } sub initialise { return if defined $data; $data = { 1 => "one", 2 => "two", 100 => "one hundred", }; } sub name { my $self = shift; my $value = shift; $self->{name} = $value if defined $value; return $self->{name} // "<unnamed object>"; } # Silly example method that returns the "text" keyed by %$data. sub text { my $self = shift; my $query = shift or return ''; # Sanity check, making sure the package namespace $data exists: die "Uh-oh, data is not defined!" unless defined $data; my $s = sprintf( "%s: %s is %s", $self->name(), $query, $data->{$query} // "<query value not defined>" ); return $s; } # Explicit RC to support require() and use(). # This is only useful when the package is a real module file. 1;
    Notice how the inclusion of an initialise() function gets rid of the BEGIN block, useful in avoiding paying the piper if you don't actually create any objects of this class for whatever reason. Also notice that the use of an accessor shifts all of the code for that accessor to one clearly defined location, so if you decide to change anything about that piece of data it won't affect anything else (this becomes a lot more important when you start having very complex objects with complex rules defining how items are updated and default values).

    I also like using named parameters for the constructor, even when only passing one or two parameters. It makes inheritance and future expansion of the class a lot simpler. Combining that with using accessors to get and set values makes the constructor incredibly simple.

      Notice how the inclusion of an initialise() function gets rid of the BEGIN block, useful in avoiding paying the piper if you don't actually create any objects of this class for whatever reason

      I like the potential flexibility there, although I'll also (for the sake of other readers and devil's advocacy) point out that if a caller wanted to put off overhead of loading a module, it's best done in a require() call in whatever conditional requires that module.

      Simply calling require() causes Perl to do extra work (searching the @INC path for a matching module, reading it in, and so on) can be overhead a consuming codebase can avoid if it's well-written. Of course, if the design requires it know in advance the support is there, or the program cannot function without the library, it's best to take the performance hit upfront.

      Not to say efficiency is a bad thing, but I also try to avoid premature optimization, or saving a library/package consumer from their own bad design ;). Where I really like your suggestion is when a package has the potential to do some relatively expensive setup that's only required in certain cases. Maybe a huge amount of data needs to be parsed to do a particular task, yet callers might just want to do a more trivial task that the package supports. Here I think your point about delaying such expensive initialization could have great benefits.


      Also notice that the use of an accessor shifts all of the code for that accessor to one clearly defined location, ...

      Yup, I appreciate the reminder, but the proof-of-concept was intentionally terse to limit the scope. I'm definitely an advocate of accessors, ideally with similar (or at least well-documented) syntax/design to the methods used to set those same object settings/values.


      I also like using named parameters for the constructor, even when only passing one or two parameters. It makes inheritance and future expansion of the class a lot simpler.

      That's a good point. I've often stuck with a single value when I had no intention of expanding a constructor, but that breaks down when the design does change in the future; being friendly to subclassing, possibly by other authors, is a great reason to consider named key/value hashes even in the simple case.

        I like the potential flexibility there, although I'll also (for the sake of other readers and devil's advocacy) point out that if a caller wanted to put off overhead of loading a module, it's best done in a require() call in whatever conditional requires that module.

        Actually, all you're putting off is running the import (which in an OO class you aren't likely to be running anyway). All of the code sitting at the class level will still run:

        package Foo; use strict; use warnings; BEGIN { print "BEGIN called\n"; } print "Class level logic\n"; sub new { my $class = shift; return bless {}, $class; } 1;
        In the above both print statements run, regardless of whether you use or require the package.

        Not to say efficiency is a bad thing, but I also try to avoid premature optimization

        Hmm, maybe I shouldn't have mentioned the additional side-benefits. Its not an attempt at optimisation, its merely a side-benefit of the design. At the end of the day though, its really just a matter of style. If you find it easier to put your initialisation logic at the class level, then thats where it should go. More important than how things should be done is consistency in your code-base :)

Log In?
Username:
Password:

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

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

    No recent polls found