2

So these days I'm working with a project that uses Perl and Moose. I understand Moose is built on MOP. I'm not too familiar with MOP, and I've encountered something I don't understand, and I could use a theoretical explanation. Here is the module namespace::autoclean's documentation:

SYNOPSIS
    package Foo;
    use namespace::autoclean;
    use Some::Package qw/imported_function/;

    sub bar { imported_function('stuff') }

    # later on:
    Foo->bar;               # works
    Foo->imported_function; # will fail. imported_function got cleaned after compilation

So, back before I ever used Moose, the way that you called a method on an object was: the Perl interpreter would look up that method in the symbol table of the package that your object was blessed into (then, if not found, consider @ISA inheritance and the like). The way it called an imported function from within the package was: it looked up the name of the function in the symbol table of the package. As far as I've been aware to date, that means the same symbol table, either way, so this behavior should be impossible.

My initial inspection of the source was not productive. In broad terms, what is different when using Moose, MOP, and namespace::autoclean, that this sort of trickery becomes possible?

ed. To be especially clear, if I were to replace use namespace::autoclean with

CHECK { undef *Foo::imported_function }

then the Foo->bar; call described in the documentation would crash, because Foo->bar doesn't know where to find imported_function.

  • Just on a long shot: would predeclaring via `use subs qw/imported_function/;` stop the autocleaning? Or importing *before* loading `namespace::autoclean`? – amon Feb 04 '13 at 18:08

2 Answers2

3

It's actually quite simple. For

some_sub()

some_sub is resolved at compile time. For

$o->some_method()

some_method is resolved at runtime. It cannot be done at compile-time since it depends on the value of $o.

amon
  • 57,091
  • 2
  • 89
  • 149
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • 1
    Ah. And so if I did `sub bar { no strict 'refs'; my $func = "imported_function"; &$func('stuff') }` it *wouldn't* work. –  Feb 04 '13 at 19:23
1

There is nothing here that is non-standard. The line

use Some::Package qw/imported_function/;

imports imported_function into the current package, so Foo::imported_function is the same subroutine as Some::Package::imported_function. That assumes that Some::Package inherits from Exporter to do the necessary manipulation of the symbol tables.

The calls are method calls, so Foo->bar is the same as Foo::bar('Foo'). The only special thing here is that the magic that has been done by the import function from Exporter is undone at the end of compile time by namespace::autoclean.

I haven't looked at the code for this module, but since a package's symbol table is just a hash (known as a stash, for symbol table hash) it would be easy to preserve its state at one point and restore it afterwards. So I would guess namespace::autoclean takes a snapshot of the symbol table when it is loaded and the restores that state at the end of compilation time. This can conveniently be done in a CHECK block which behaves like a BEGIN block but is executed at the end of compilation and before the run starts.

Borodin
  • 126,100
  • 9
  • 70
  • 144
  • "So I would guess namespace::autoclean takes a snapshot of the symbol table when it is loaded and the restores that state at the end of compilation time." Compile-time manipulation of the symbol table *is* straightforward, but the resolution of the imported method call `sub bar { imported_function('stuff') }` which happens when you call `Foo->bar` ... **occurs at runtime**. You will observe, for instance, that this doesn't work: https://gist.github.com/4708620 Clearly, this is not a full explanation of the mechanism that is at work. –  Feb 04 '13 at 18:42
  • @fennec Of course it does. However, you `undef`ed the sub itself, instead of deleting only the name from the stash → `delete ${Foo::}{pseudoimport}`, and it works. (for many names: `delete @{Foo::}{@list_of_names};`). Of course, such barbarism destroys *any* variable with the same name as well). – amon Feb 04 '13 at 18:48
  • Okay, that's interesting. So where does the code for Foo::pseudoimport live after `delete ${Foo::}{pseudoimport}`? –  Feb 04 '13 at 18:56
  • @fennec You can have anonymous subs: `my $anon = sub {...};` Where does that live? *Not* in the stash. It is accessible as long as it has a reference count. Names and values are distinct: `my $x = 1; $hash{key} = $x; delete $hash{key} # does $x still have a value?` Delete just removes an entry from a hash, and doesn't touch the value – amon Feb 04 '13 at 19:00
  • Hmm. It seems you're saying that my assertion that the resolution of `pseudoimport()` occurs at runtime is **false** and I was wrong. If so, that's simple enough and explains everything. –  Feb 04 '13 at 19:04