10

I have a subroutine called debug I use in my code. It basically allows me to see what's going on, etc.

sub debug {
    my $message      = shift;
    my $messageLevel = shift;

    our $debugLevel;
    $messageLevel = 1 if not defined $messageLevel;
    return if $messageLevel > $debugLevel;
    my $printMessage = "    " x $messageLevel . "DEBUG: $message\n";
    print STDERR $printMessage;
    return $printMessage;
}

I want to prototype this, so I can do things like this:

debug "Here I am! And the value of foo is $foo";

or

debug "I am in subroutine foo", 3;

At the same time, I like putting subroutine definitions at the bottom of my program, so you don't have to wade 1/2 way through the code to find the meat of the program.

I'd like to do this:

sub debug($;$);  #Prototype debug subroutine

/Here goes the main program code/

sub debug {      #The entire subroutine goes here
   /Here goes the debug subroutine code/
}

However, I get a warning when I do this:

Prototype mismatch: sub main::debug ($;$) vs none at foo.pl line 249.

So, I'm stuck putting the prototype definition in both places. What is the correct way to do something like this?


RESPONSE

Stop! Module time. – Chris Lutz

A module? You mean create a separate file? That adds a bit of complication without solving the issue I'm trying to solve: Removing the need for parentheses around this particular subroutine.

our $debugLevel; should not be in the sub body anyway, but I agree with Chris on this. – Sinan Ünür 3 hours ago

The our $debugLevel does not have to be there in this case, but if I defined a class and I want to use this subroutine in my class for debugging, I need it. I can put it in my class as ::debug

Surprisingly, Far more than everything you ever wanted to know about prototypes in Perl doesn't address this, but I believe you cannot avoid writing the prototype in both places.

I was hoping for an easy way to avoid it. There is a way as Eric Strom showed. Unfortunately, it's longer than my debug routine.

I used to use prototypes, but I've developed the habit of not writing separate declarations for subroutines and using parentheses on all calls: debug("I am in subroutine foo", 3);. It's been suggested that prototypes really aren't a good idea. TMTOWTDI – Keith Thompson 3 hours

Except I'll tend to do:

debug (qq(The value of Foo is "$foo"), 3);

which can be less clear when reading, and can be a pain to type. Whenever you double up parenthese, you're asking for trouble. The last thing I want to do is debug my debug statements.

Why do you want prototypes? See this question How to pass optional parameters to a Perl function – TLP

Yes, there are lots of problems with prototyping. The main problem is that it simply doesn't do what people think it should do: Declare the variable types for the parameters you're passing to your function.

This is not the reason I'm using prototyping here.

I rarely use prototypes. In fact, this is probably the only case in all of my code where I do.

Community
  • 1
  • 1
David W.
  • 105,218
  • 39
  • 216
  • 337
  • 1
    `our $debugLevel;` should not be in the sub body anyway, but I agree with Chris on this. – Sinan Ünür Nov 14 '11 at 22:18
  • 4
    Surprisingly, http://www.perlmonks.org/?node_id=861966 doesn't address this, but I believe you cannot avoid writing the prototype in both places. – ephemient Nov 14 '11 at 22:18
  • 2
    I used to use prototypes, but I've developed the habit of *not* writing separate declarations for subroutines and using parentheses on all calls: `debug("I am in subroutine foo", 3);`. It's been suggested that prototypes really aren't a good idea. TMTOWTDI – Keith Thompson Nov 14 '11 at 22:39
  • 2
    Why do you want prototypes? See this recent question http://stackoverflow.com/q/8124138/725418 – TLP Nov 14 '11 at 23:06
  • See my **RESPONSE** at the end of my original post. – David W. Nov 15 '11 at 03:03
  • Related (but that is for the command-line): *[How can I pass command-line arguments to a Perl program?](https://stackoverflow.com/questions/361752/how-can-i-pass-command-line-arguments-to-a-perl-program)* – Peter Mortensen Oct 06 '22 at 10:58

5 Answers5

25

Just get rid of prototypes altogether:

sub debug;

debug "Here I am! And the value of foo is $foo";
debug "I am in subroutine foo", 3;

sub debug {
    # body of debug
}
Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
  • 1
    Wholeheartedly agree. Prototypes in Perl are not used the same way as in other languages, and are not useful for the same things as in other languages. See the link in ephemient's comment, above. – mob Nov 14 '11 at 23:27
  • Except in this case, I'm using prototypes for its intended purpose: allowing me to use a subroutine without parentheses much like you can with built-in functions. What you should not use prototypes for is for type checking. – David W. Nov 15 '11 at 02:19
  • 8
    @David W - that is just wrong. It is not a prototype that makes the parentheses optional. – mob Nov 15 '11 at 03:10
  • 3
    @David - mob is correct. The thing that makes parentheses optional is the declaration that `debug` is a subroutine. – Ted Hopp Nov 15 '11 at 03:28
  • 1
    Damn. I always thought you had to use prototypes to eliminate parentheses. – David W. Nov 15 '11 at 03:38
  • two sides of the same coin, no prototype means the same thing as the `(@)` prototype... it is the fact that the subroutine is in the compiling package's symbol table at compile time that really matters. – Eric Strom Nov 15 '11 at 04:15
8

The prototype is attached to the coderef and not the name, so when you replace the coderef with the new declaration, you are clearing the prototype. You can avoid having to cross-reference and match prototypes with a helper function:

sub debug ($;$);

debug 'foo';

use Scalar::Util 'set_prototype';
sub install {
    my ($name, $code) = @_;
    my $glob = do {no strict 'refs'; \*$name};
    set_prototype \&$code, prototype \&$glob;
    *$glob = $code;
}

BEGIN {
    install debug => sub {
        print "body of debug: @_\n";
    };
}

install is just a wrapper around Scalar::Util's set_prototype function, which allows you to change the prototype of a coderef after it is created.

Prototypes can be very useful, but when using the scalar prototype, always ask yourself if that is really what you intended. Because the ($;$) prototype tells perl "debug is a function that can take one or two arguments, each with scalar context imposed on the call site".

The bit about context is where people usually get tripped up, because then if you try to do this:

my @array = qw(one two);

debug @array;

Then @array gets seen in scalar context, and becomes 2. So the call becomes debug 2; rather than debug 'one', 'two'; as you might have expected.

Eric Strom
  • 39,821
  • 2
  • 80
  • 152
  • Yeah, I know the limits of prototyping. In this case, I'm using it as pretty much intended. I want to be able to type `debug "Some statement";` and maybe attach a debug level like `debug "Some statement", 2;` without worrying about parentheses. The helper function is interesting, but its even longer than my little debug subroutine. Still, I like it although I'll probably just do it as I've done it before. – David W. Nov 15 '11 at 02:24
  • Yes, I about this particular little issue with `debug @array`. The problem is I want to sometimes include a debug level, but not necessarily. If I pass in @array, I don't know if that's one array, or an array with a debug level on the end. I know what I pass to the routine **must** be a scalar. – David W. Nov 15 '11 at 03:07
  • Fair enough. The SO police will shoot downvotes without warning if that disclaimer isn't there about anything regarding prototypes. In fact, I think I got hit with one while typing it... – Eric Strom Nov 15 '11 at 04:18
  • +1 for mentioning `Scalar::Util 'set_prototype'`. That was the key for a problem I was having, which lead me to this SO post. :-) – Francisco Zarabozo Aug 24 '13 at 10:31
6

If I am understanding correctly, you want to prototype and predeclare so that you can use the function (prototyped and braceless) within the same file. This is what the subs pragma is for.

For example, this code works correctly:

#!/usr/bin/env perl

use strict;
use warnings;

use subs qw/mysay/;

mysay "Yo";
mysay "Yo", "Joel";

sub mysay ($;$) {
  my $message = shift;
  my $speaker = shift;
  if (defined $speaker) {
    $message = "$speaker says: " . $message;
  }
  print $message, "\n";
}
Joel Berger
  • 20,180
  • 5
  • 49
  • 104
  • Well then, I can just get rid of prototypes. The only reason I thought I had to use them was to use the function without parentheses. – David W. Nov 15 '11 at 03:39
  • @DavidW. nope, all it needs is to be predeclared. prototypes force context or other specific uses of lists after the function. For more see [`perldoc perlsub`], "the intent of this feature is primarily to let you define subroutines that work like built-in functions"(http://perldoc.perl.org/perlsub.html#Prototypes) – Joel Berger Nov 15 '11 at 04:06
  • @DavidW. a good example is if you need a function to take two distinct arrays AND NOT to flatten them into a single list, like `push` does. Most of the time one would do this by having the function take array references rather than the arrays, but if you want to have a cleaner interface, then prototypes are the answer. You should rarely need them. – Joel Berger Nov 15 '11 at 04:12
2

You have to declare the same prototype when you define your subroutine:

sub debug($;$); # prototype/declare

... meat of the program ...

sub debug($;$) {
    ...
}
dlamotte
  • 6,145
  • 4
  • 31
  • 40
  • The author implied in the question that he was aware of this. Also, I think there is a way to do it without duplicating the prototype. – Sam Jun 04 '12 at 09:41
0

Here's how to do it:

sub debug;  #Prototype debug subroutine

#Here goes the main program code/

sub debug($;$) {
   #Here goes the debug subroutine code/
}
Sam
  • 40,644
  • 36
  • 176
  • 219