http://www.perlmonks.org?node_id=209238

I was reading an article about how Perl behaves internally when I realized I can create some very simple code that does not produce the output I expect. Consider a typeglob (I know, you don't want to, but bear with me). A typeglob is merely an identifier, preceded by an asterisk that refers to a slot in a symbol table. This 'slot' refers to all possible types of the identifier (hence the term "typeglob"). So the typeglob *main::foo refers to, amongst other things, $main::foo, %main::foo, &main::foo, etc. Lexically scoped variables are not in typeglobs (though a reference to one might be), only package variables. Also, note that if you're referring to the %main:: namespace, the "main" is optional; $::foo is the same as $main::foo.

I use typeglobs quite a bit when I'm writing test suites so that I can override the behavior of a subroutine in my code. For example, I might override the behavior of carp().

my $carp; *SomePackage::carp = sub { $carp = join '', @_ };

That now allows me to test when carp() has been called and test if the error message is appropriate. Reducing this to a one-liner, we can see how defining a subroutine via a typeglob works.

$ perl -e '*::foo = sub{3};print foo()' 3

Now that's a pretty nifty trick that many people like, many dislike, and I'll just stay out of that debate. Now, here's the question. What does the following print and why does it print it? Fair warning, I have not given you enough information to answer this. (and I can easily see this stunt being abused in obfuscations).

$ perl -e 'sub foo(){2};*::foo=sub(){3};print foo()'

I'll post a link later to the article that I mentioned. Also, if you know the answer, wait a while before responding, or post it with a spoiler warning with black on black.

Cheers,
Ovid

Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

Replies are listed 'Best First'.
Re: Little Perl Mysteries: what's your answer?
by robartes (Priest) on Oct 30, 2002 at 22:21 UTC
    And my 2 cents worth..
    A function prototyped to accept no arguments is treated as a candidate for inlining by the Perl compiler. In this case sub foo(){2} will be inlined because it returns a constant. The snippet will thus print 2, because print foo(); is turned into print 2; by the compiler, no matter what you do to the definition of sub foo later on at runtime.
    Nice one, Ovid!

    CU
    Robartes-

      and the proof is:
      c:\>perl -MO=Deparse -e "sub foo(){2};*::foo=sub(){3};print foo();" sub foo () { 2; } *foo = sub () { 3; } ; print 2; -e syntax OK

      cheers,
      Aldo

      King of Laziness, Wizard of Impatience, Lord of Hubris

Re: Little Perl Mysteries: what's your answer?
by Juerd (Abbot) on Oct 31, 2002 at 10:06 UTC

    Note: spoiler, black text on a black background. Select the text to read it. On some systems, you might have to paste it before it's actually visible. (And I put in some glow-in-the-dark hyperlinks *grin* (code may also be visible, if you defined a background color for it, in your PM CSS)). Update: Grumble. There's an HTML filter that filtered out the bgcolor of my <tr> tag. Putting it in <td> seems to be allowed.

    robartes is right.

    (Note: I'll be using the words 'compile-time' and 'run-time', even though there is no clear distinction in Perl which is when.)

    The first definition is happening at compile-time, the second is at run-time. One might expect that in print foo(), a subroutine is called, and that that happens at run-time, thus using the new definition of foo().

    But there is no subroutine call. Perl tries to be smart, and optimizes the code. The foo() has become a constant, the actual print that does happen is just print 2, as dada points out.

    podmaster was able to redefine the function effectively, because BEGIN blocks are executed as soon as possible (i.e. immediately), and redefinition takes place before run-time and before the foo() "call" is encountered. Other compile-time tricks would allow redefinition too, like having the code in a .pm file and using use.

    You get the expected 3 if you force Perl to actually call the subroutine . You can do that by using the & sigil. This disables prototype checking and inlining (probably because inlining depends on the prototype).

    perl -le'sub foo(){2};*::foo=sub(){3};print &foo()'
    Other ways of avoiding Perl to inline foo() are:
    print *foo{CODE}(); print eval 'foo()';

    (I think I found a bug in my B::Deparse. perl -e'sub foo(){2}' doesn't even display the definition of foo, while dada's Deparse does display inlined subroutines. It's not as if the sub is completely gone: perl -le'sub foo(){2} print *foo{CODE}()' does work as expected. Perl 5.8.0 on linux x86. Please confirm.)

    - Yes, I reinvent wheels.
    - Spam: Visit eurotraQ.
    

      robartes appears to be the first to have correctly answered the question, but I'll respond to this as you've answered more in depth and you have a question that needs answering.

      The article that I read, by Sean Burke, is about Constants in Perl. It discusses some a traditional compiler optimization known as "constant folding". Essentially, this occurs when, for a given operations, if all of the operands are known at compile time and cannot change, then the compiler goes ahead and precomputes the resulting value to save a bit of time.

      perl -MO=Deparse -e 'print 3*4' print 12; -e syntax OK

      Thus, what appears to be an expression is turned into a literal constant. Note that the 3*4 is now optimized away. There is no longer any remnant of that code. This, naturally, would be even more useful when it occurs in an inner loop, but the mechanism is not perfect and Perl sometimes needs to be helped. See the article for details and the snippet below for an example.

      $ perl -MO=Deparse -e '$i=6;$j=2;print 3*4*$i*$j*3*4' $i = 6; $j = 2; print 12 * $i * $j * 3 * 4; -e syntax OK

      That result is disappointing, but adding the '-p' switch to force parenthese can tell us why Perl is getting a bit confused.

      $ perl -MO=Deparse,-p -e '$i=6;$j=2;print 3*4*$i*$j*3*4' ($i = 6); ($j = 2); print(((((12 * $i) * $j) * 3) * 4)); -e syntax OK

      Tying all of that together with my snippet, we have the following quote from the third Camel.

      Inlining Constant Functions

      Functions prototyped with (), meaning that they take no arguments at all, are parsed like the time built-in. More interestingly, the compiler treats such functions as potential candidates for inlining. If the result of that function, after Perl's optimization and constant-folding pass, is either a constant or a lexically scoped scalar with no other references, then that value will be used in place of calls to that function. Calls made using &NAME are never inlined, however, just as they are not subject to any other prototype effects.

      Because Perl can optimize the sub call away, there is no longer any subroutine for the typeglob to override.

      Juerd wrote:

      I think I found a bug in my B::Deparse. perl -e'sub foo(){2}' doesn't even display the definition of foo, while dada's Deparse does display inlined subroutines. It's not as if the sub is completely gone: perl -le'sub foo(){2} print *foo{CODE}()' does work as expected. Perl 5.8.0 on linux x86. Please confirm.

      I don't believe this is a bug. I ran that on my Linux box at home with both 5.6.1 and 5.8.0 and the latter does not display the definition of the sub, but the former does. I suspect that this is actually an optimization where there is no need to have the sub's code in the bytecode if it's been optimized away. Unfortunately, I cannot find a reference to this in the 5.8.0 docs and thus cannot confirm it.

      Cheers,
      Ovid

      Thanks to larsen for pointing out my typo: s/Contacts/Constants/;

      Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

        I don't believe this is a bug.

        I think it is a bug because Deparse's output is not executable.

        1;1 juerd@ouranos:~$ perl -e'sub foo () { 42 } print foo(), "-\n"' 42- 1;0 juerd@ouranos:~$ perl -MO=Deparse -e'sub foo () { 42 } print foo() +, "-\n"' print 42, "-\n"; -e syntax OK 1;0 juerd@ouranos:~$ perl -MO=Deparse -e'sub foo () { 42 } print foo() +, "-\n"' | perl -e syntax OK 42- 1;0 juerd@ouranos:~$ perl -e'sub foo () { 42 } print *foo{CODE}(), "-\ +n"' 42- 1;0 juerd@ouranos:~$ perl -MO=Deparse -e'sub foo () { 42 } print *foo{ +CODE}(), "-\n"' print *foo{'CODE'}(), "-\n"; -e syntax OK 1;0 juerd@ouranos:~$ perl -MO=Deparse -e'sub foo () { 42 } print *foo{ +CODE}(), "-\n"' | perl -e syntax OK Undefined subroutine &main:: called at - line 1. 1;255 juerd@ouranos:~$
        (In the last example, *foo{CODE} is undef, and undef->() gives an 'Undefined subroutine &main:: called' error.)

        - Yes, I reinvent wheels.
        - Spam: Visit eurotraQ.
        

      No such trouble here - deparses fine. However, various versions of the module have different defaults for how readable they try to make the output, so you may want to try -MO=Deparse,-x7 to force no translation to be done. If it still doesn't show up, that would be a bug then I guess.

      Makeshifts last the longest.

Re: Little Perl Mysteries: what's your answer?
by dws (Chancellor) on Oct 30, 2002 at 22:14 UTC
    My guess is that...
    ... prototypes are enforced at compile time. I wonder if part of enforcement has the side-effect of "pickling" a reference to the subroutine to be invoked. If that's the case, swapping the typeglob at runtime would have no effect.
Re: Little Perl Mysteries: what's your answer?
by Aristotle (Chancellor) on Oct 30, 2002 at 21:52 UTC
    I don't know exactly why it is, but
    removing the parens for the prototype gives the expected behaviour. Obviously, then, so does adding a & to the function call to disable the prototype. So it has something to do with prototypes.
    I'm eager to hear the resolution.

    Makeshifts last the longest.

Re: Little Perl Mysteries: what's your answer?
by PodMaster (Abbot) on Oct 31, 2002 at 04:24 UTC
    I see no problem here perl -e "sub foo(){2};BEGIN{*::foo=sub(){3};}print foo()" The saying "don't use prototypes unless you know why you shouldn't" still applies.

    ____________________________________________________
    ** The Third rule of perl club is a statement of fact: pod is sexy.

Re: Little Perl Mysteries: what's your answer?
by Mr. Muskrat (Canon) on Oct 30, 2002 at 22:47 UTC

    Okay, I feel stupid now...

    The magic happens during the compile phase and not the execution phase!

    That is brilliant!

Re: Little Perl Mysteries: what's your answer?
by Jenda (Abbot) on Oct 30, 2002 at 22:26 UTC
    Ahhh ... inlined constants ... good gotcha.

    Jenda

Re: Little Perl Mysteries: what's your answer?
by John M. Dlugosz (Monsignor) on Oct 31, 2002 at 22:29 UTC
    Before reading any other replies,
    I'd say 2, because the call is statically bound by the compiler and it doesn't look up the symbol table entry at all.

    I recall this issue from the docs about overriding core functions, and when you have to declare something beyond what importing normally does.

    —John

Re: Little Perl Mysteries: what's your answer?
by Joost (Canon) on Nov 01, 2002 at 14:30 UTC
    Perl 5.8.0 linux-thread-multi croaks with a spoiler
Re: Little Perl Mysteries: what's your answer?
by rir (Vicar) on Oct 31, 2002 at 04:05 UTC

    The () have no effect on the anonymous sub, a sub ref.

    I'm wrong.