3

Perl has limited support for static code checking, in particular it can check whether we pass appropriate number of argument to a function. For example this will result in error:

use strict;
use warnings;
sub f($) {}
f(2, 3)

Too many arguments for main::f

If we try to call f too early we will get another useful warning:

use strict;
use warnings;
f(2, 3)
sub f($) {}

main::f() called too early to check prototype

This will result in error:

use strict;
use warnings;
sub f($) {}
sub g() { f(2, 3) }

Too many arguments for main::f

And my question is if we can get such message for the following scenario:

use strict;
use warnings;
sub g() { f(2, 3) }
sub f($) {}

Because I do not get any error or warning here. It would be good if I could prevent this code from compiling. Please advise.

agsamek
  • 8,734
  • 11
  • 36
  • 43
  • 1
    I think you've found bug in this mechanism. – teodozjan Jun 03 '11 at 13:55
  • 6
    Why are you using prototypes? – converter42 Jun 03 '11 at 14:08
  • 3
    I want better static checks, too. Prototypes are not a good way as they are fraught with the many complications pointed out below and in other threads on SO. Let's build and add it as a proper mechanism to Perl. – daxim Jun 03 '11 at 15:04
  • 5
    You're misusing prototypes. See [Why are Perl 5's function prototypes bad?](http://stackoverflow.com/questions/297034). – Michael Carman Jun 03 '11 at 16:21
  • @converter42 (and upvoters) I have a slightly bigger project and prototypes identify some bugs statically at little cost. – agsamek Jun 03 '11 at 16:28
  • @agsamek - wrong answer. Perl prototypes will almost certainly fail you. Please read the post Michael Carman linked above and this article on perlmonks: http://perlmonks.org/?node=861966 – converter42 Jun 03 '11 at 16:55
  • 2
    I concur, prototypes are the wrong way to solve this problem. They are not prototypes like in other languages. They are mostly used to modify the context of a subroutine's arguments and emulate built-in functions. If you don't know what that means, *don't use prototypes*. There are alternatives. – Schwern Jun 03 '11 at 20:13
  • 1
    See [Far More than Everything You've Ever Wanted to Know about Prototypes in Perl -- by Tom Christiansen](http://www.perlmonks.org/?node_id=861966). – David W. Jun 03 '11 at 20:43
  • 1
    @agsamek: Using prototypes this way may find some bugs but it will introduce others and they will be nasty, subtle bugs that are very hard to find. Implicit context coercion can seriously bite you in the backside. For example, `f(@a)` would not be caught by "static checking" -- it's a valid call. At runtime it would pass in the *length* of `@a` because a `$` prototype means "impose scalar context" and that's what an array returns when evaluated in scalar context. – Michael Carman Jun 03 '11 at 21:49

3 Answers3

11

"Those in the know" (most notably Damian Conway in his book "Perl Best Practices") advise not to use subroutine prototypes as they only serve to obfuscate code by "making it impossible to deduce the argument-passing behavior of a subroutine call simply by looking at the call."

Rob Raisch
  • 17,040
  • 4
  • 48
  • 58
  • And I had a problem with Getopt::Long once because it did have a prototype on some of its functions, and accordingly mangled the data I was passing it. – Colin Fine Jun 03 '11 at 15:07
  • Hmmmm - congrats on earning 5 points and not touching the question ;) BTW - Perl is nice because it promotes diversity. – agsamek Jun 03 '11 at 16:21
  • 1
    TMTOWTDI (http://en.wikipedia.org/wiki/There's_more_than_one_way_to_do_it) also implies there are both good and not-so-good ways to do something. In the case of validating subroutine arguments, you can do it the good way (handling validation programmatically and throwing errors as needed) or the not-so-good way, by using subroutine prototypes. Handling validation yourself means you'll never hit one of the many gotchas using prototypes. – Rob Raisch Jun 03 '11 at 16:45
2

Yes you can, by pre-declaring it before you actually use it:

use strict;
use warnings;
sub f($);                     # prototype defined
sub g() { f(2, 3) }
sub f($) { print "hi\n"; }
Wes Hardaker
  • 21,735
  • 2
  • 38
  • 69
  • OK - this would be an idea, it it was possible to prevent all invocations to undeclared subs. Otherwise it does not help at all. Is it possible? – agsamek Jun 03 '11 at 16:19
2

Do not use prototypes to do argument checks for all the myriad reasons already pointed out. There is temptation to do so because they provide compile-time checks, but their checks are limited to stupid mistakes, they don't check method calls, and they have unexpected side effects.

Use a module such as Method::Signatures to do real argument checking and also provide you with argument processing.

use Method::Signatures;

use strict;
use warnings;

func g() { f(2, 3) }

func f($foo) {
    print "f got $foo\n";
}

The check will happen when the subroutine is run (there's a bug right now where it doesn't check that you passed too many arguments, fixing that) but it gives you a far richer tool set to check your arguments (types coming shortly) than prototypes without all the nasty side effects of prototypes. And it works on method calls, prototypes do not.

Schwern
  • 153,029
  • 25
  • 195
  • 336