2

I am trying to call a prototype function from a class without instantiating an object. An example of my class MyClass :

package MyClass;
use strict;
use warnings;

sub import{
        my $class = shift;
        my ($caller) = caller();
        eval "sub ${caller}::myprot(\&);";
        eval "*${caller}::myprot = \&MyClass::myprot;";        
}

sub myprot (&) {
    my ($f) = @_;
        $f->();
}

1;

I want to call the prototype from a script main.pl:

use strict;
use warnings;

use MyClass;

myprot {
        print "myprot\n";
};

and I am getting the errors:

Use of uninitialized value in subroutine entry at MyClass.pm line 14.
Use of uninitialized value in subroutine entry at MyClass.pm line 14.
Undefined subroutine &main::myprot called at main.pm line 8.

I don't really understand the undefined subroutine error: With use, import is called which defines the prototype for main.pl. I also really don't understand the uninitialised value error. I'd be happy for some explanation.

user1981275
  • 13,002
  • 8
  • 72
  • 101
  • I think you need `myprot (sub { print "myprot\n"});` and you also need to export myprot from your your MyClass packgage to do it like that http://stackoverflow.com/questions/17912400/export-vs-export-ok-in-perl – KeepCalmAndCarryOn Dec 01 '14 at 19:47
  • @KeepCalmAndCarryOn: That isn't valid Perl - presumably you meant to use braces `{..}` instead of parentheses `(..)`. And no - he wants `MyClass` to *declare* a function `myprot` that takes a block of code as its only parameter. But I agree that he shouldn't have made it look like a subroutine definition without the `sub`. – Borodin Dec 01 '14 at 19:57
  • I trust that you aren't expecting to actually *use* this in live code? My answer explains why it isn't working for you, but you *really must not* do anything like this outside experimentation. – Borodin Dec 01 '14 at 20:06
  • there's no need for the first eval; forward declarations are only needed with autoloading or if the subroutine will be defined later, but here you are exporting it immediately. But just use Exporter instead – ysth Dec 01 '14 at 20:16
  • Your error messages come from the fact that your eval is happening before the subroutine table is filled in the caller. Look into [the regarding documentation](http://perldoc.perl.org/perlmod.html#BEGIN,-UNITCHECK,-CHECK,-INIT-and-END) and have in mind, that `use` is said to be roughly equal to `BEGIN{require module; module::import(args)}`. Also, your `eval`s miss some backslashes. – Patrick J. S. Dec 02 '14 at 00:46

3 Answers3

8

You're looking for Exporter.

package MyClass;
use strict;
use warnings;

use Exporter qw( import );

our @EXPORT = qw( myprot );

sub myprot(&) {
    my ($f) = @_;
    $f->();
}

1;

I usually use @EXPORT_OK (requiring the use of use MyClass qw( myprot );) rather than exporting by default.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • This works well, thanks! But I still don't get exactly what `Exporter` does. If I don't use `use` for instance, but `require MyClass;` and `MyClass->import();`, then `import` is executed but I can't call `myprot` – user1981275 Dec 01 '14 at 21:40
  • 1
    You're trying to compile the call to `myprot` before `myprot` (and its prototype) has been declared. `use MyClass;` is basically `BEGIN { require MyClass; MyClass->import(); }`, so you moved the import to run-time. – ikegami Dec 01 '14 at 21:44
  • 1
    @user1981275 Long story short, Exporter provides your module with an import routine that does most of what you want. If you don't understand how `use` and `import` interact, don't write your own import routine. The docs for [import](http://perldoc.perl.org/functions/import.html), [use](http://perldoc.perl.org/functions/use.html) and [how use works in modules](http://perldoc.perl.org/perlmod.html#Perl-Modules) might help with understanding. [Prototypes](http://perldoc.perl.org/perlsub.html#Prototypes) are also a pit of caveats and should not be used without a solid understanding. – Schwern Dec 01 '14 at 21:46
  • @ikegami ok, makes sense to me now. – user1981275 Dec 01 '14 at 22:04
5

There's a bunch of sketchy things going on in that code.

Unchecked use of eval means if it fails, you'll never know. eval should be used as eval "code" or die $@. You'll find it's throwing an error because strict does not like it when you mess with the symbol table (that's what *name = \&code is doing).

Using eval to export subroutines is overkill. eval STRING is a potential security hole and should be used as a last resort (eval BLOCK is fine). You can manipulate the symbol table without eval, but strict will not like the use of symbolic references.

my $caller = "foo";
*{"${caller}::myprot"} = \&MyClass::myprot;
# Can't use string ("foo::myprot") as a symbol ref while "strict refs" in use...

You have to turn off strict first. This is generally known as "aliasing".

no strict 'refs';
*{$caller.'::myprot'} = \&myprot;

Setting the prototype beforehand is unnecessary, the alias will take care of it for you.

It turns out this is all unnecessary, there's a number of modules which do this for you. The most common one is Exporter and comes with Perl. This makes your custom import unnecessary.

use Exporter 'import';
our @EXPORT = qw(myprot);

Other general tips...

Hard coding the name of a class in a class (ie. \&MyClass::myprot should just be \&myprot) should be avoided. It makes it harder to change the class or move the code around.

Hybrid modules which are both classes and export functions, are discouraged. They're harder to use, test and document and produce odd side effects. You should put myprot into its own module.

Schwern
  • 153,029
  • 25
  • 195
  • 336
  • That's a neat round-up. But I suspect (hope) the OP was playing with the arcane corners of Perl and wasn't expecting to use it in live code. – Borodin Dec 01 '14 at 20:02
  • @Borodin I'm pretty confident of the opposite. Fortunately, they have both answers now. :) – Schwern Dec 01 '14 at 20:03
  • Really? Only he can know. I will ask. – Borodin Dec 01 '14 at 20:04
  • How protective are you of your formatting? I started to make adjustments to your post but soon realised that you are a *"two spaces before a full stop"* person. Would it upset you if that was changed? – Borodin Dec 01 '14 at 20:08
  • 2
    Exporter doesn't need inheritance, so don't use it: `use Exporter 'import';`, not `use parent 'Exporter';` – ysth Dec 01 '14 at 20:13
  • @ikegami You're right, symbol table manipulation is fine. Symbolic references are not. There's a lot of things wrong with that eval. – Schwern Dec 01 '14 at 20:16
  • @ysth I don't know what version of Perl they're using, so I went with a conservative approach. Do you know when `use Exporter 'import'` was added? – Schwern Dec 01 '14 at 20:17
  • The unescaped `\ ` is the only mistake. The eval is used in lieu of sym refs. Disabling strict refs in this type of code is simpler, as you showed. – ikegami Dec 01 '14 at 20:18
  • @Borodin Please, enjoy yourself. – Schwern Dec 01 '14 at 20:18
  • @ysth It's older than parent, didn't know that. Thanks! – Schwern Dec 01 '14 at 20:27
  • this works well, thanks for the explanation! I'll go with `eval` .. or die. So that was the thing, that `import` wasn't called... – user1981275 Dec 01 '14 at 20:33
  • 2
    no, import was called and your reinvent-the-wheel-instead-of-just-using-Exporter failed – ysth Dec 01 '14 at 20:41
  • 3
    @user1981275 I cannot recommend enough that you avoid eval in general and avoid reinventing Exporter. It's very, very, very easy to get wrong in subtle ways and can be a maintenance pit. There are valid reasons to write your own import, but this is not one of them. – Schwern Dec 01 '14 at 21:40
  • @Schwern I see that this is a bit messy. But is there another clean way to have the convenience in `main` to just call a function from `MyClass` with only a codeblock in the fashion shown in the example? – user1981275 Dec 01 '14 at 21:50
  • @user1981275 Using the `&` prototype to look like a code block is one of the few valid reasons to use prototypes, go ahead and play with that. There's no need to write your own import routine to make `myprot` available to other packages, Exporter will do it for you and it will preserve the prototype. – Schwern Dec 04 '14 at 17:37
1

Are you sure you really want to do this?

The problem is that the double quotes will eat the backslash you have in the glob assignment.

eval "*${caller}::myprot = \&MyClass::myprot;"

should be

eval "*${caller}::myprot = \\&MyClass::myprot;"

But please don't ask me to debug your code!

Borodin
  • 126,100
  • 9
  • 70
  • 144