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

I recently posted a question in which it was kindly mentioned that my use of prototypes was probably inappropriate. I went ahead and did some reading on it, and I'm still a bit curious and have a few questions.

First, why *doesn't* Perl support strict function-argument specification? I can see that prototypes don't really do this, but... I'm surprised that there is no easy way to enforce it. Is this a result of the way Perl seamlessly casts between ints and strings?

Second, many of the prototype-problems seem to come down to the functions accepting "weirdness" despite the prototype. But from the article it did appear that passing reference to data-structures (e.g. sub foo(\%) {}) wasn't too problematic: mainly coming down to issues of "semantic sugar" rather than unexpected behaviour. Is that a misconception?

It still seems "clearer" to my eyes to see the data-types expected of a function specified in its declaration. I'm trying to "adjust" my eye-sight by just taking it out and putting it in comments at the start of the routine, but I figured I should try to understand why I'm doing it this way as best as possible!

Replies are listed 'Best First'.
Re: Considering Prototypes
by tall_man (Parson) on Jul 28, 2003 at 18:26 UTC
    You can do run-time parameter checking if you want to in perl. For example, see Params::Validate. What perl can't easily support is compile-time checking (one reason being that all function defintions and calls aren't available at compile time, because of things like eval and require).

    For an interesting discussion about strong typing and perl, see the thread Griping about Typing.

Re: Considering Prototypes
by sauoq (Abbot) on Jul 28, 2003 at 19:19 UTC
    it did appear that passing reference to data-structures (e.g. sub foo(\%) {}) wasn't too problematic: mainly coming down to issues of "semantic sugar" rather than unexpected behaviour. Is that a misconception?

    I think it is, at least somewhat. Some of it comes down to who is doing the expecting, of course. I think it might help to show an example.

    sub foo { my $array_reference = shift; print "foo: ", $array_reference->[0], "\n"; } sub bar (\@) { my $array_reference = shift; print "bar: ", $array_reference->[0], "\n"; } my @a1 = ( 1 ); my @a2 = ( \@a1 ); my $a3 = [ 3 ]; foo(@a1); foo(@a2); foo($a3); bar(@a1); bar(@a2); # bar($a3); # Can't do that because it would be a runtime error. bar(@$a3);

    The first thing to notice is that both subs expect an array reference, right? Well, yes and no. They both want to receive an array reference, but the prototype'd sub forces the programmer to pass an array, not a reference to one. That's why bar($a3) errors out. It forces you to write bar(@$a3) instead. But that seems unnatural for a sub which is expecting a reference.

    The second issue is that, with prototypes, the normal array flattening behavior that we've come to expect doesn't work any more. We can't stuff the arguments into an array and pass them to the prototyped sub. See the difference between foo(@a2) and bar(@a2)? It's worse when there is more than one argument. A function like sub baz (\@\@) { ... } expects to be called as baz(@qux, @quux) but without the prototype, those two arrays would be flattened into one. All of this might not be so bad if it weren't for issue three...

    Where prototypes really fail is that they aren't enforced. Remember bar($a3)? Runtime error. That is, unless you call it as &bar($a3) in which case the prototype is circumvented. Same problem if you want to work with a reference to the bar sub.

    my $subref = \&bar; $subref->(@a1); # Doesn't act like bar(@a1)...
    And it won't work if bar() is a method call either. And again, these problems are even stickier when the sub in question is prototyped to take more than one argument like baz() above. Prototypes are circumvented, argument lists are flattened, and weird bugs appear without any nice error or warning messages to help you find them.

    Basically, Perl's prototypes foster inconsistency when used as if they were real prototypes. There are a few good reasons to use them. Three that I know of:

    1. An empty prototype on a sub hints that a sub is a candidate for inlining.
    2. Creating subroutines that act like builtins.
    3. Extending the language syntax. (By writing subs that take a BLOCK.)

    -sauoq
    "My two cents aren't worth a dime.";
    
Re: Considering Prototypes
by chunlou (Curate) on Jul 28, 2003 at 18:42 UTC

    It's true that lack of "strict function-argument specification" or its "signature-less" subroutines could be annoying at times.

    It's useful however when you need high level of abstraction. Consider the following pseudo self contained code excerpt:

    #! /usr/local/bin/perl -w use strict ; # ################################################################# # Schema: Survey Database my %Survey = ( tblq => { primID => ['q_id'], cols => ['anstmpl_id', 'short_label', 'question' +, 'updated'] }, tblqtmpl => { primID => ['qtmpl_id'], cols => ['name', 'descr', 'updated'] }, tblqtmpl_q => { primID => ['qtmpl_id', 'q_id'], cols => ['seq', 'notes', 'updated'] }, ); # ################################################################# # Package: Survey Database {package Survey ; # assume %Survey defined somewhere # = = = = = = = = = = = = = = = = Contructor = = = = = = = = = = = + = = = = = = # sub dbconnect { } # = = = = = = = = = = = = = = = Public Methods = = = = = = = = = = + = = = = = = # foreach my $tbl (keys %Survey){ # define subroutines <action>_<tab +le> at runtime eval qq/sub insert_$tbl {return (shift)->_insert_table(_tblnam +e(),\@_)}/ ; eval qq/sub update_$tbl {return (shift)->_update_table(_tblnam +e(),\@_)}/ ; eval qq/sub delete_$tbl {return (shift)->_delete_table(_tblnam +e(),\@_)}/ ; } # = = = = = = = = = = = = = = = Private Methods = = = = = = = = = += = = = = = =# sub _insert_table { my ($self, $tbl, $values) = (shift, shift, shift) ; # ... and more... } sub _update_table { my ($self, $tbl, $values) = (shift, shift, shift) ; # ... and more... } sub _delete_table { my ($self, $tbl, $values) = (shift, shift, shift) ; # ... and more... } # -------------------------------------------------------------- sub _tblname{ # return <tbl>, if called by <action>_<tbl> e.g. s +how_tblanstype ( my $sub = (caller(1))[3] ) =~ s/.*::.*?_(.*)/$1/ ; return $sub ; } } # ################################################################# # Test Script: Survey Database Package # tblq $Survey->insert_tblq(\%data) ; $Survey->update_tblq(\%data) ; $Survey->delete_tblq(\%data) ; # tblqtmpl $Survey->insert_tblqtmpl(\%data) ; $Survey->update_tblqtmpl(\%data) ; $Survey->delete_tblqtmpl(\%data) ; # tblqtmpl_q $Survey->insert_tblqtmpl_q(\%data) ; $Survey->update_tblqtmpl_q(\%data) ; $Survey->delete_tblqtmpl_q(\%data) ;

    The focus is such lines as eval qq/sub insert_$tbl {return (shift)->_insert_table(_tblname(),\@_)}/;. It generates subroutines at compile time. Having strict function argument specification may not allow you such a coding. Not necessarily good or bad, just another way to do things.


    ______________________
    Footnote: As a side question, why not something likeinsert($table, $data) instead of insert_thistable($data)? Well, if you're refactoring existing code and some other scripts have been using insert_thistable($data), you have no choice but keep the interface.

Re: Considering Prototypes
by waswas-fng (Curate) on Jul 28, 2003 at 19:08 UTC
    As was pointed out in Conways article, it will help you to not think of perl prototypes as prototpes. Calling them prototypes was a mistake as they force most programmers that hae used other languages to view them (incorectly) as strict function argument specifiers. I guess the whole thing with why perl does not really support strict typing in the core is because that so much of the perl Do The Right Thing behavior relys on dynamicaly taking vars of different types and acting the right way. IMHO it is one of the wonderful things about this language.

    -Waswas
Re: Considering Prototypes
by adrianh (Chancellor) on Jul 28, 2003 at 19:58 UTC
    First, why *doesn't* Perl support strict function-argument specification? I can see that prototypes don't really do this, but... I'm surprised that there is no easy way to enforce it. Is this a result of the way Perl seamlessly casts between ints and strings?

    The only answer is really "because". Perl5 decided to keep the argument passing strategy very simple. This does have advantages on occasion - and can be surprisingly elegant.

    Along with everything else, you'll get it in Perl6 - see Apocalypse 6 for the full details.

Re: Considering Prototypes
by scrottie (Scribe) on Jul 29, 2003 at 05:52 UTC
    Hi Anonymous Monk.

    When a language doesn't have something, 99% of the people using that language see absolutely no reason for it. Consider that the set of features that a language has changes with time, and perhaps this will strike you as amusing as it strikes me.

    I agree with you - type safety (strong types, prototypes, what have you) have a time and place. A lot of people think Perl doesn't have a time and place, so the gut reaction of Perlers is to assume that the language that these people use is the one that actually doesn't have a time and place. Given two school children, it is a logical imparative that one of them must stink.

    It just so happens that I also saw the need for the TypeSafety, and wrote typesafety.pm. No, I'm not kidding. typesafety.pm uses the B backend to traverse the bytecode of your modules. It examines type declarations, and it makes sure that arguments are correct, method calls get the correct return values, consistent with those declarations. I forgot to check a few things (return values on the returning end as well as the receiving end), but if there is any interest (it would be the first time there is any interest in any of my modules), I'll add it.

    Some excellent links were posted, and I'm adding them to TypeSafety on PerlDesignPatterns with glee, but to summerize why I think type safety is sometimes not stinky:

    When you can't remember what is returning or accepting what and you'd like the language to remind you. When you're refactoring and moving things around, and you'd like to be told what you've broken. When you're checking for strange values anyway and it is cluttering your program. You're dealing with a complex design and making it more concrete will help you see the shortfalls of it - specifically, you're being too general in some places (expecting Pets where you only want Dog objects) or not general enough (a routine should be able to deal with all Pets, not just Dogs). Formalizing your types helps you realize what is a subset of what, and what subsets what routines should be prepared to deal with. It also encourages you to break out things into types and subtypes, which helps maintain order and refactorability in a large program, especially one with multiple authors, especially when those multiple authors are all working on the code at the same time.

    Penny for my thoughts?
    -scott
Re: Considering Prototypes
by Anonymous Monk on Jul 29, 2003 at 19:27 UTC

    Hi all,

    I think I'm starting to grok it. While Perl does allow prototypes, it doesn't really enforce them and even in the cases where they aren't riddled with pitfalls (references) they can still trip up novices (me me ME!). Especially if one expects them to behave as they do in other languages.

    Based on the Apocalypse, I think Perl6 functions will be much easier for C/Java based programmer to understand. I keep on getting twisted on C-like Perl looks, but how... well, non-C-like it acts.

    Thanks again!