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

Compare this two piece of one-liner code that try to achieve the same thing, i.e. overriding backtick a.k.a. readpipe():

% perl -e'package Foo; sub readpipe { die }; sub import { my $caller = caller(); *{"$caller\::readpipe"} = \&readpipe } package main; BEGIN { Foo->import }; print readpipe("ls")'
Died at -e line 1.
% perl -e'package Foo; sub readpipe { die }; package main; BEGIN { *{"main::readpipe"} = \&Foo::readpipe } print readpipe("ls")'
file1
...

Why does the first code work, while the second doesn't? In the second code I've also tried putting the whole Foo code inside BEGIN block, without avail.

  • Comment on Overriding built-in function works only via import()?

Replies are listed 'Best First'.
Re: Overriding built-in function works only via import()?
by haukex (Archbishop) on Feb 01, 2018 at 09:47 UTC

    Although I'm not sure of the "why" at the moment (Update: see my other post), Overriding Built in Functions does say:

    Overriding may be done only by importing the name from a module at compile time--ordinary predeclaration isn't good enough. However, the use subs pragma lets you, in effect, predeclare subs via the import syntax, and these names may then override built-in ones:
    $ perl -wMstrict -le 'use subs "readpipe"; print `Bar`; sub readpipe { +"Foo"}' Foo
      I should've read perlsub first. Thanks, haukex. I'd guess the reason for requiring this is to make it less likely to accidentally override builtins (e.g. think of an old Perl program declaring its own "say" subroutine).
Re: Overriding built-in function works only via import()?
by Athanasius (Archbishop) on Feb 01, 2018 at 07:07 UTC

    Hello perlancar,

    With warnings enabled, the second code gives me:

    17:01 >perl -we "package Foo; sub readpipe { die }; package main; BEGI +N { my $rp = qq[main::readpipe]; *{ $rp } = \&Foo::readpipe; } print +readpipe('ls')" Ambiguous call resolved as CORE::readpipe(), qualify as such or use & +at -e line 1. 1801_SoPW.pl 1802_SoPW.pl 1803_CUfP ...

    I don’t understand why the compiler sees the call as ambiguous; but following the advice in the warning and prepending an ampersand to the readpipe function does give the desired result:

    17:01 >perl -we "package Foo; sub readpipe { die }; package main; BEGI +N { my $rp = qq[main::readpipe]; *{ $rp } = \&Foo::readpipe; } print +&readpipe('ls')" Died at -e line 1. 17:04 >

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      Having to use ampersand in the "target code" removes the attraction to do this in the first place :)
Re: Overriding built-in function works only via import()?
by haukex (Archbishop) on Feb 01, 2018 at 19:11 UTC
    Why does the first code work, while the second doesn't?

    The Camel (🐪, Chapter 11) to the rescue:

    Overriding Built-in Functions ... it's the assignment of a code reference to a typeglob that triggers the override, as in *open = \&myopen. Furthermore, the assignment must occur in some other package; this makes accidental overriding through typeglob aliasing intentionally difficult.

    And looking at git history, the corresponding language was in perlsub as early as perl-5.000. So the behavior appears to be quite intentional.

    And BTW, here's another short way to do what you want that doesn't use subs:

    BEGIN { package Foo; *main::readpipe = sub {"Foo"} } print `xyz`; __END__ Foo
      Consequently it shouldn't work if the import() is called in the same package, right? (Untested)

      Hence the perldocs should be clearer:

      > perlsub#Overriding-Built-in-Functions Overriding may be done only by importing the name from a module at compile time--ordinary predeclaration isn't good enough. 

      A module is a .pm which supports importing with use It's normal practice to have its own package, but not necessarily so.

      Cheers Rolf
      (addicted to the Perl Programming Language and ☆☆☆☆ :)
      Wikisyntax for the Monastery

        Consequently it shouldn't work if the import() is called in the same package, right? (Untested)

        Yep, looks like it:

        $ perl -wle '{ package Foo; sub import { *{caller."::readpipe"} = sub {"Foo"} } } BEGIN { Foo->import() } print `xyz`' Foo $ perl -wle 'sub import { *{caller."::readpipe"} = sub {"Foo"} } BEGIN { main->import() } print `xyz`' Can't exec "xyz": No such file or directory at -e line 2.
Re: Overriding built-in function works only via import()?
by ikegami (Patriarch) on Feb 01, 2018 at 17:37 UTC

    Yes, it has to be imported from another package to override. That's what subs is for.

    $ perl -e' sub readpipe { die }; readpipe("ls"); ' $ perl -e' use subs qw( readpipe ); sub readpipe { die } readpipe("ls"); ' Died at -e line 3.