Search code examples
perl

Why Perl subroutine prototype `foo()` can take parameters?


use 5.016;

sub foo() {
    say "foo";
}

sub main() {
    foo(42);
}

main();

If I run the code above, it will show the errors below, this is what I expected because foo takes no argument:

Too many arguments for main::foo at b.pl line 8, near "42)"
Execution of b.pl aborted due to compilation errors.

use 5.016;

sub main() {
    foo(42);
}

sub foo() {
    say "foo";
}

main()

But if I run the code above, it can run correctly:

foo

Why foo accepts parameters this time?


Solution

  • Prototypes change parsing rules for the functions declared with them. So in order to be taken into account they must be known before the function call is parsed, either by a preceding definition or a pre-declaration (sub foo();) of the function.

    If a function call comes before the function's definition or pre-declaration were seen it can only be parsed as a normal (unprototyped) call, assuming that the interpreter can figure out that it is a function in the first place. (In this example it can because of parenthesis.)

    Then once the definition comes up the indicated prototype can have no effect on the call already parsed. However, further calls of the function, following its definition (or predeclaration), are prototyped. With another bit added:

    use 5.016;
    
    fun_proto(42);      # runtime warning: 
                        # "called too early to check prototype"
    
    sub fun {
        fun_proto(42);  # parsed as a normal sub as no prototype has been seen
    }
    
    sub fun_proto() { 
        say "@_";
    }
    
    fun();              # fun_proto(42) call in 'fun' is OK
    
    fun_proto(43);      # compilation error: extra argument by prototype 
    

    A direct call before the function definition draws a warning at runtime, but not the call inside another sub. I don't know why the call from a sub doesn't get the warning. (The call of the sub that has the fun_proto(42) call appears after the fun_proto definition but all that -- the enclosing sub and fun_proto call in it -- gets compiled before the definition.)

    To fix code like this add a pre-declaration ("forward declaration") before any mention of the function. In this example that would be a statement sub fun_proto();

    Perhaps you know exactly what you are doing but I have to ask: are the prototypes really needed in your code? They come with subtleties while their purpose is merely to allow calling user defined subs the way builtins are called, not to check number and types of arguments.

    If the purpose is to check arguments then since v5.20 perl has proper subroutine signatures.


    One real problem with this is that the code is compiled differently depending on prototypes, so when they aren't applied while they were intended (like here) that may lead to quiet errors. A simple example: with

    sub fun() { ... }
    

    when that sub is called (without parenthesis) nothing that follows its invocation is taken for its arguments -- because it takes no arguments! -- so

    fun + 3     # intended to be compiled with regards to () prototype
    

    later (tricky as it is) runs as

    fun() + 3   # sub declared w/ prototype to take no arguments
    

    However, if that prototype definition didn't apply to this call, like in the example in this question, then what follows the sub in the expression is taken as the list of its arguments so the code is compiled as

    fun(+3)     # oups, not intended (prototype unused by error)
    

    No warnings.