Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options
 
PerlMonks  

Re^3: help with versioning modules

by dsheroh (Monsignor)
on Nov 21, 2020 at 12:45 UTC ( #11123967=note: print w/replies, xml ) Need Help??


in reply to Re^2: help with versioning modules
in thread help with versioning modules

Here's the thing... Package name and filesystem path have (almost1) no intrinsic connection at all. The only reason they're normally the same is because of convention. But you should absolutely follow this convention because, even though Perl can handle it without problems, it easily becomes very, very confusing for humans trying to use the code, as your post here illustrates.

I suspect the reason you're not quite seeing the significance of the distinction is that, in this case, your package name and filesystem path are almost, but not quite, the same, so it's easy to gloss over that difference in your head. So let's rewrite things a bit to make it more obvious. In a file at My/Bar.pm, put

package Xyzzy; sub say_it { print "It works!"; }
Now, in your code, you load this file with use My::Bar; but there is no package named My::Bar, or even a package named Bar. The package is named Xyzzy, so you must call the sub with Xyzzy::say_it (or Xyzzy->say_it if you prefer, since it will just ignore all parameters passed anyhow).

If you move the module file to Some/Random/Place.pm, you will then load it with use Some::Random::Place;, but the package inside is still package Xyzzy, so you still have to call the code with Xyzzy::say_it, not Some::Random::Place::say_it. The path you use the module at and the package within the module are two separate things.


1 There is one exception. The one and only time that Perl cares whether the path and the package name match up is when you export symbols. This is because use Whatever; is equivalent to require Whatever; Whatever->import(); so, if the file Whatever.pm contains package Something::Else, and Something::Else exports symbols, that won't work because the use will implicitly call Whatever->import() (which doesn't exist) and not Something::Else->import().

Replies are listed 'Best First'.
Re^4: help with versioning modules
by Special_K (Monk) on Nov 23, 2020 at 01:19 UTC

    Thanks for the additional information, this is starting to make more sense now. Is it correct that in the context of a "use" statement (example: use My::Foo), the :: effectively represent a filesystem directory separator character (e.g. '/' in Linux), the last word with a ".pm" appended to it represents the module's filename, and this path (My::Foo, e.g. /My/Foo.pm) is then appended to each base path in @INC to determine where Perl will search for the listed module?

    If I'm understanding everything correctly, my original example is failing because the use My::Foo 1.0 statement is using My::Foo as a search path to find My/Foo.pm (relative to /home/user/perl_modules/lib/perl5) but it is also treating My::Foo as a package name to look for a version variable My::Foo::$VERSION. The only way both of these can be correct is if the package name matches the exact name specified to use the module. I was able to fix everything by either doing this:

    use lib '/home/user/perl_modules/lib/perl5/My'; use Foo 1.0;

    Or by renaming the package to My::Foo and then updating the code references accordingly.
    Also after I created the My/Bar.pm file as written in your post and then another file to "use My::Bar" and then call the Xyzzy->say_it function, I received the following error:

    My/Bar.pm did not return a true value at ./bar_test.pl line 4. BEGIN failed--compilation aborted at ./bar_test.pl line 4.

    I was able to fix this by adding a "1;" to the end of Bar.pm. Do all packages need to end with "1;"?

    > The package is named Xyzzy, so you must call the sub with Xyzzy::say_it (or Xyzzy->say_it if you prefer, since it will just ignore all parameters passed anyhow).

    Can you elaborate on this a little bit? What exactly is the difference between calling say_it with :: vs ->?

      Is it correct that in the context of a "use" statement (example: use My::Foo), the :: effectively represent a filesystem directory separator character (e.g. '/' in Linux), the last word with a ".pm" appended to it represents the module's filename, and this path (My::Foo, e.g. /My/Foo.pm) is then appended to each base path in @INC to determine where Perl will search for the listed module?
      Correct, or close enough for all practical purposes. (What you describe matches my understanding of it, but I suspect there may be some subtle edge case which prevents it from being absolutely 100% accurate.)
      If I'm understanding everything correctly, my original example is failing because the use My::Foo 1.0 statement is using My::Foo as a search path to find My/Foo.pm (relative to /home/user/perl_modules/lib/perl5) but it is also treating My::Foo as a package name to look for a version variable My::Foo::$VERSION. The only way both of these can be correct is if the package name matches the exact name specified to use the module.
      Yep. And that makes two cases where it matters to Perl whether the two names match up - I never include version reuirements in use, so I forgot about that one.
      I was able to fix this by adding a "1;" to the end of Bar.pm. Do all packages need to end with "1;"?
      All files included via use or require need to return a true value (not the number 0, the string "0", the empty string, or undef) from the last statement in the file. Putting the line 1; at the end of the file is the standard way to ensure that this happens.

      If the print statement in my example module had been outside the sub, it would have sufficed, but, because it's inside a sub which doesn't get called, it doesn't actually run when the file is loaded, so the file inclusion returns undef instead. (Oops. That's what I get for not testing my example code.)

      What exactly is the difference between calling say_it with :: vs ->?
      :: just specifies the package, so that you can directly call a sub or access a variable that's defined in a package other than the current package. It doesn't actually do anything other than letting you give the "full name" of a symbol.

      -> is a part of how Perl does object-orientation and has a little bit of magic to let it fulfill that role. The first bit of magic is that, if you have a blessed reference to the left of the arrow, it will look for the right side in the package that the left side was blessed into. The second magical effect is that, when the sub on the right side is called, then whatever is on the left side of the arrow is passed as the first parameter, prior to any parameters that are explicitly given. (Which is what my comment earlier was referring to - if you don't look at the parameters, the extra one doesn't matter.)

      I realize that explanation of the magic is so brief as to be unclear, so here are some examples:

      Xyzzy::say_it; # Calls sub say_it in package Xyzzy with an empty parameter list Xyzzy->say_it; # Calls sub say_it in package Xyzzy with the parameters ('Xyzzy') my $obj = Some::Class->new(foo => 'bar'); # Calls sub new in package Some::Class with the parameters # ('Some::Class', 'foo', 'bar') # # By convention, 'new' will bless its return value into the class # (package) matching its first parameter ('Some::Class'), making # $obj an instance of that class $obj->do_stuff(1, 2, 3); # Because $obj is blessed as an instance of Some::Class, calls # Some::Class::do_stuff with the parameters ($obj, 1, 2, 3)
Re^4: help with versioning modules
by Special_K (Monk) on Jan 05, 2021 at 00:58 UTC

    I was studying your examples again and came up with another question. Suppose I take your My/Bar.pm file and copy it as-is to a new file My/Xyzzy.pm. Now suppose I create a new file xyzzy_version_test.pl to reference and use this new module as follows:

    #!/tool/bin/perl -w use strict; use lib '/home/user/perl_modules/lib/perl5/My'; use Xyzzy; say_it();

    Now the package name matches the file name. Running this code gives the following error:

    Undefined subroutine &main::say_it called at ./xyzzy_version_test.pl l +ine 6.

    It seems that the package name Xyzzy is still explicitly required in this case to call the say_it function even though the package name now matches the filename. After doing some searching, I found that adding the following to My/Xyzzy.pm allows the above call to say_it() (without the Xyzzy->) to work:

    use Exporter qw(import); our @EXPORT = qw(say_it); our @EXPORT_OK = qw();

    My question is: are the above 3 lines of code that I added to My/Xyzzy.pm (and using qw() to reference specific subroutines in a package, e.g. use Xyzzy qw(say_it);, had I included say_it in the EXPORT_OK list rather than the EXPORT list) only used to prevent the user from having to prefix every call to a exported subroutine with Package-> every time, or do they serve other purposes?

      Pretty much, yes - the primary purpose of exporting symbols (sub or variable names) is just so that you don't need to provide the fully-qualified name every time you reference the symbol.

      The error message you got hints at the internals behind this - it complains that it couldn't find &main::say_it, but, because the sub is in package Xyzzy, its actual fully-qualified name is &Xyzzy::say_it. Exporting the sub creates a new reference to say_it in the package that you use the module from (which, in this case, defaults to package main, since you aren't explicitly inside a different package).

      When you're writing object-oriented code (which is where the -> syntax is normally used), you generally don't need (or want) to export the subs implementing your methods, because they should be called from an object reference ($my_object->do_stuff()), so exporting is usually only used for non-OO procedural interfaces where the subs would be called as stand-alone chunks of code.

      Also, as a side note regarding EXPORT vs. EXPORT_OK, it's generally considered better to use EXPORT_OK, so that symbols are only exported by explicit request. This helps to avoid conflicts (what if you had already defined your own sub say_it, then you use Xyzzy and it implicitly exports its own say_it?) and makes it easier to locate where things came from while debugging (if you use 15 modules that all implicitly export subs, you can't find where say_it was defined without checking every one of those modules, but use Xyzzy qw(say_it) makes it obvious where it came from).

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others wandering the Monastery: (2)
As of 2021-04-17 08:04 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?