-1

I've got a Perl script that I'm trying to make compatible with two different Perl environments. To work around the two different versions of Socket I have, I'm doing a little hackery with require and import. I've got it working, but I'm not happy with the behavior.

Mod.pm:

package Mod;
use base 'Exporter';
our @EXPORT = qw( MAGIC_CONST );
sub MAGIC_CONST() { 42; }

test.pl:

use Mod;
#require Mod;
#import Mod;

printf "MAGIC_CONST = ". MAGIC_CONST ."\n";
printf "MAGIC_CONST = ". MAGIC_CONST() ."\n";

Outputs:

MAGIC_CONST = 42
MAGIC_CONST = 42

But using the 'require' and 'import' instead, I get this:

Output:

MAGIC_CONST = MAGIC_CONST
MAGIC_CONST = 42

So the question is: Is there a clean way I can get the normal behavior of the constants? I can certainly do sub MAGIC_CONST { Mod::MAGIC_CONST(); } but that's pretty ugly.

What I'm actually doing is something like this:

use Socket;
if ($Socket::VERSION > 1.96) {
  import Socket qw(SO_KEEPALIVE); # among others
  setsockopt($s, SOL_SOCKET, SO_KEEPALIVE); # among others
}
TOertel
  • 29
  • 7
  • 1
    Which versions of Socket are those? Do you also have different versions of Perl? There is no `use strict`? – simbabque May 17 '17 at 12:24
  • It's Socket 1.82 vs 2.010. Obviously if I could upgrade the older version I would... but can't. Yes, perl is also very different. But test above runs same on both versions of perl. – TOertel May 17 '17 at 12:27
  • 1
    You can tell us the Perl versions anyway. If you don't know the answer, don't assume you can decide which information is relevant to finding it. ;) – simbabque May 17 '17 at 12:28
  • 4
    Why do you not want to do `use`? The difference between `use` and `require` is that `use` gets done at compile time, and `require` is not. So when you call `" . MAGIC_CONST . "` with the `require`, Perl does not know about the symbol `MAGIC_CONST` yet during the compilation phase, as the `require` will only be done at run time. Without `use strict`, barewords are treated as strings if they are not symbols, which explains why you see the text `MAGIC_CONST` instead of `42`. – simbabque May 17 '17 at 12:31
  • perl 5.10.1 and perl 5.16.3 – TOertel May 17 '17 at 12:32
  • 3
    I second the `use strict` suggestion. If you need more control than `use Mod`, then you can write `BEGIN { require Mod; Mod->import }` to ensure it happens early enough to get `MAGIC_CONST` defined during parsing. But why do you need that? Are you choosing which variant of Mod to load? This sounds a bit like an [X Y problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) to me. – nothingmuch May 17 '17 at 12:42
  • 1
    I am also not entirely sure I understand the question. I've explained the behavior in my comment above, but I am not sure that is sufficient to answer it. If it is, I'll convert it to an answer and flesh it out. However to answer the direct question, if you want it to behave like `use`, use `use` or do the `BEGIN { ... }` block like @nothingmuch suggested in their comment. – simbabque May 17 '17 at 12:48
  • I'm not clear on how `use strict` actually improves the situation. I added my actual partial "solution", to avoid the YX problem. ;) I didn't appreciate the difference between the BEGIN{} and the post-BEGIN import. – TOertel May 17 '17 at 12:55
  • I prefer and recommend [the combination with `Const::Fast` and `Importer`](https://www.nu42.com/2016/05/perl-define-constants.html) rather than `constant.pm` for defining constants to be used by other modules. With `Const::Fast`, you can also namespace your constants to specific constant hashes. – Sinan Ünür May 17 '17 at 14:02
  • 2
    `use strict` would avoid the interpretation of the bareword as a string, as explained in Adam's answer. This would have made it clearer that the constant is not actually imported until it's too late, even though the line that imports it appears sooner in the file. Wrapping the importing in a `BEGIN` block [makes that execute at compile time](https://perldoc.perl.org/perlmod.html#BEGIN%2c-UNITCHECK%2c-CHECK%2c-INIT-and-END), which means Perl can then [parse](https://perldoc.perl.org/perldata.html#Identifier-parsing) the rest of the code interpreting those barewords as subroutine names. – nothingmuch May 17 '17 at 14:10

2 Answers2

4

The reason the require version prints MAGIC_CONST instead of 42 is because use is what tells perl to import the symbols from one module to another. Without the use, there is no function called MAGIC_CONST defined, so perl interprets it as a string instead. You should use strict to disable the automatic conversion of barewords like that into strings.

#!/usr/bin/env perl
no strict;
# forgot to define constant MAGIC_CONST...
print 'Not strict:' . MAGIC_CONST . "\n";

produces

Not strict:MAGIC_CONST

But

#!/usr/bin/env perl
use strict;
# forgot to define constant MAGIC_CONST...
print 'Strict:' . MAGIC_CONST . "\n";

Produces an error:

Bareword "MAGIC_CONST" not allowed while "strict subs" in use at ./test.pl line 4. Execution of ./test.pl aborted due to compilation errors.

So if you want to use one module's functions in another, you either have to import them with use, or call them with the full package name:

package Foo;
sub MAGIC_CONST { 42 };

package Bar;
print 'Foo from Bar: ' . Foo::MAGIC_CONST . "\n";

Foo from Bar: 42

It's usually best to avoid conditionally importing things. You could resolve your problem as follows:

use Socket;
if ($Socket::VERSION > 1.96) {
  setsockopt($s, SOL_SOCKET, Socket::SO_KEEPALIVE);
}

If you truly want to import, you still need to do it at compile-time.

use Socket;
use constant qw( );
BEGIN {
  if ($Socket::VERSION > 1.96) {
     Socket->import(qw( SO_KEEPALIVE ));
  } else {
     constant->import({ SO_KEEPALIVE => undef });
  }
}

setsockopt($s, SOL_SOCKET, SO_KEEPALIVE) if defined(SO_KEEPALIVE);
ikegami
  • 367,544
  • 15
  • 269
  • 518
Adam Millerchip
  • 20,844
  • 5
  • 51
  • 74
3

Adam's answer gives a good explanation of what is going on and how to get the desired behavior. I am going to recommend not using constant.pm to define symbolic names for constants. A fairly nice looking and convenient alternative with fewer gotchas is to use constants defined using Const::Fast and allow them to be imported.

In addition, by using Importer in the module which wants to import the constants, the module that defines the constants can avoid inheriting Exporter's heavy baggage, or having to use Exporter's import.

The fact that Const::Fast allows you to define real constant arrays and hashes is a bonus.

For example:

package MyConstants;

use strict;
use warnings;

use Const::Fast;
use Socket;

const our @EXPORT => ();
const our @EXPORT_OK => qw(
    %SOCKET_OPT
);

const our %SOCKET_OPT => (
    keep_alive => ($Socket::VERSION > 1.96) ? Socket::SO_KEEPALIVE : undef,
);

__PACKAGE__;
__END__

Using these constants in a script:

#!/usr/bin/env perl

use strict;
use warnings;

use Socket;
use Importer 'MyConstants' => qw( %SOCKET_OPT );

if ( defined $SOCKET_OPT{keep_alive} ) {
    setsockopt($s, SOL_SOCKET, $SOCKET_OPT{keep_alive});
}

As I note in my blog post:

f I want to read the constants from a configuration file, that's trivial. If I want to export them to JSON, YAML, INI, or whatever else, that's also trivial. I can interpolate them willy-nilly.

For those who have taken seriously Exporter's stance against exporting variables, this takes some getting used to. Keep in mind that the admonition is there to make sure you don't write code that willy nilly modifies global variables. However, in this case, the variables we are exporting are not modifiable. Maybe you can convince yourself that the concern really does not apply in this instance. If you try to refer to a non-existent constant, you get an error (albeit during run time) ...

In most cases, the benefits of this approach outweigh the speed penalty of not using constant.pm.

Community
  • 1
  • 1
Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339