Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

Sub signatures, and a vexing parse

by davido (Cardinal)
on Nov 18, 2014 at 21:53 UTC ( [id://1107656]=perlmeditation: print w/replies, xml ) Need Help??

I was experimenting with the experimental subroutine signatures feature of Perl 5.20 today along with the much maligned prototypes feature of old, and encountered a most vexing parse that interested me. So I wanted to mention it here.

First, something that is not a problem:

*mysub = sub : prototype(\@\@) ($left,$right) { ... };

This parses correctly, and will generate a subroutine named mysub with a prototype of \@\@, and with named parameters of $left and $right, which when called will contain array refs. But this doesn't do much. My real goal was generating several similar subroutines, and called upon map in a BEGIN{ ... } block to do the heavy lifting.

Here is a contrived example that isn't terribly useful, but that works, and demonstrates the issue:

use strict; use warnings; no warnings 'experimental::signatures'; use feature qw/say signatures/; use List::Util qw(all); BEGIN { ( *array_numeq,*array_streq ) = map { my $compare = $_; sub :prototype(\@\@) ($l,$r) { @$l == @$r && all { $compare->($l->[$_],$r->[$_]) } 0 .. $#$l } } sub { shift == shift }, sub { shift eq shift } } my @left = ( 1, 2, 3 ); my @right = ( 1, 2, 3 ); { local $" = ','; say "(@left) ", ( array_numeq @left, @right ) ? "matches" : "doesn't match", " (@right)"; }

Do you see what the problem is? The compiler doesn't care for this at all, and will throw a pretty useless compiletime error:

Array found where operator expected at mytest2.pl line 14, at end of l +ine (Missing operator before ?) syntax error at mytest2.pl line 14, near "@\@) " Global symbol "$l" requires explicit package name at mytest2.pl line 1 +4. Global symbol "$r" requires explicit package name at mytest2.pl line 1 +4. Global symbol "$l" requires explicit package name at mytest2.pl line 1 +5. Global symbol "$r" requires explicit package name at mytest2.pl line 1 +5. Global symbol "$l" requires explicit package name at mytest2.pl line 1 +6. Global symbol "$r" requires explicit package name at mytest2.pl line 1 +6. Global symbol "$l" requires explicit package name at mytest2.pl line 1 +7. BEGIN not safe after errors--compilation aborted at mytest2.pl line 17 +.

Q: So what changed between the first example, that works, and the second example, that doesn't?

A: Lacking other cues, the compiler parses  sub : as a label named sub, and thinks that I'm trying to call a subroutine named prototype... and from that point on things are totally out of whack.

Solution: +. Anything that can remind the parser that it's not looking at a label will do the trick. Parenthesis around the sub : ... construct works, but + is easier, and probably more familiar to programmers who use + to get {....} to be treated as an anonymous hash ref constructor rather than as a lexical block.

With that in mind, here's code that works:

use strict; use warnings; no warnings 'experimental::signatures'; use feature qw/say signatures/; use List::Util qw(all); BEGIN { ( *array_numeq,*array_streq ) = map { my $compare = $_; + sub :prototype(\@\@) ($l,$r) { @$l == @$r && all { $compare->($l->[$_],$r->[$_]) } 0 .. $#$l } } sub { shift == shift }, sub { shift eq shift } } my @left = ( 1, 2, 3 ); my @right = ( 1, 2, 3 ); { local $" = ','; say "(@left) ", ( array_numeq @left, @right ) ? "matches" : "doesn't match", " (@right)"; }

...or how a single keystroke de-vexed the parse.

A really simple example that breaks is this:

my $subref = do{ sub : prototype($) ($s) { return $s; }; # Perl thinks sub: is a lab +el here. };

I don't really see any way around the parsing confusion in the original version that doesn't work. That perl considers sub : to be a label in the absence of other cues is probably not something that can be fixed without making sub an illegal label. But if I were to file a bug report (which I haven't done yet), it would probably be related to the useless error message.

This example is fairly contrived, but it's not impossible to think that subs with signatures and prototypes might be generated in some similar way as to fall prey to this mis-parse.

Credit to mst and mauke on irc.perl.org#perl for deciphering why the compiler fails to DWIW.


Dave

Replies are listed 'Best First'.
Re: Sub signatures, and a vexing parse
by Eily (Monsignor) on Nov 19, 2014 at 13:31 UTC

    I would have upvoted more than once if I could. I'm pretty sure to never do something like that (I still never add to use attributes, and the very few times I used prototypes were on named subroutines) but it was an interesting read nonetheless!

    But you could avoid the issue altogether, and in my opinion get a clearer code by setting the prototype or attribute after the declaration:

    use Sub::Util qw/set_prototype/; (*Hello, *Hi) = map { set_prototype '\@\@' => $_ } map { my $var = $_; + sub { $_; } } qw/Hello Hi/;
    I also thought about
    use attributes(); sub Hi { "Hi" }; attributes::->import(__PACKAGE__, *{$_}{CODE}, 'prototype(\@\@)') for +*Hi;
    but I can't test it with v5.14 (which doesn't have prototype attributes), and it does not work with the 'lvalue' attribute:
    use v5.14; use attributes(); use Data::Dumper; my $Hi; sub Hi:lvalue { $Hi; } my $Hello; sub Hello { $Hello; } BEGIN { attributes::->import(__PACKAGE__, \&Hello, 'lvalue'); } Hi = 'monks'; # It kind of looks like a sigil-less variable actually : +) Hello = 'world'; # There would be a warning here without the BEGIN blo +ck say "Hi: ", Hi; say "Hello: ", Hello; __END__ Hi: monks Hello:

Re: Sub signatures, and a vexing parse
by Anonymous Monk on Nov 19, 2014 at 00:20 UTC
    Do you see what the problem is?
    Yes, get rid of this :prototype(\@\@) thing. Why does it even exist.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://1107656]
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others exploiting the Monastery: (6)
As of 2024-04-19 11:04 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found