LanX has asked for the wisdom of the Perl Monks concerning the following question:
This is a continuation of a CB discussion, started by JadeNB which puzzles me too
Why does a (&) prototype only accept blocks or sub{} but not scalar coderefs?
I always thought that sub{} returns a scalar coderef.
See following code, line 36 shows a workaround from Ikegami.
But why the heck do I need an idempotent operation like \& ???
DB<32> sub fu(&) {print shift}
DB<33> fu sub {}
CODE(0x990f0a8)
DB<34> $a= sub {}
DB<35> fu $a #ERROR
Type of arg 1 to main::fu must be block or sub {} (not scalar derefere
+nce) at (eval 37)[/usr/share/perl/5.10/perl5db.pl:638] line 4, at EOF
DB<36> fu \&$a #OK
CODE(0x997f168)
UPDATE: Thanks JadeNB nice problem, I learned a lot! 8)
Re: coderefs and (&) prototypes
by ikegami (Patriarch) on Jul 28, 2009 at 02:08 UTC
|
Why does a (&) prototype only accept blocks or sub{} but not scalar coderefs?
It does. In fact, that's the only thing it accepts. The real question is why doesn't it accept arbitrary expressions that return a code ref. And the answer is that there's no way to guarantee that the expression will return a code ref. Rather than adding code to the optree to do run-time type checking, prototypes only allow operators that are guaranteed to return a value of the appropriate type.
For example,
The arg for (\@) must start with a @.
The arg for (\%) must start with a %.
sub {} is guaranteed to return a code ref, so it's allowed for (&).
\&cb is guaranteed to return a code ref, so it's allowed for (&). By extension, this includes \&{ EXPR }.
&foo, on the other hand, is a sub call. It can return anything. It's not allowed for (&) (or (\@) or (\%)).
| [reply] [d/l] [select] |
|
The real question is why doesn't it accept arbitrary expressions that return a code ref. And the answer is that there's no way to guarantee that the expression will return a code ref.
and then
\&cb is guaranteed to return a code ref, so it's allowed for (&). By extension, this includes \&{ EXPR }.
I don't understand these two together. There's no way to guarantee that EXPR in \&{ EXPR } will evaluate to a code-ref either, and, if it doesn't, the extra \& noise isn't going to prevent it from blowing up at run-time—so why put the programmer through that extra syntactic hassle, if it doesn't buy him or her any extra safety?
| [reply] [d/l] [select] |
|
the extra \& noise isn't going to prevent it from blowing up at run-time
The whole point of parameter validation is to blow up. Why would you want to prevent it?
Without the "extra noise", it wouldn't blow up. That's the problem.
sub f0 { my ($cb) = @_; '...' }
sub f1(&) { my ($cb) = @_; '...' }
f0(\&abc); # OK No error
f0(\&{ maybe_coderef() }); # OK Error caught at run-time (if any)
f0("abc"); # XX Error uncaught
f0(maybe_coderef()); # XX Error uncaught (if any)
f1(\&abc); # OK No error
f1(\&{ maybe_coderef() }); # OK Error caught at run-time (if any)
f1("abc"); # OK Error caught at compile-time
f1(maybe_coderef()); # OK Error caught at compile-time (if any)
Update: Added example | [reply] [d/l] |
Re: coderefs and (&) prototypes
by LanX (Saint) on Jul 27, 2009 at 22:30 UTC
|
OK now that I can read my own question, the answer seems clear ...
the parser can't decide at compile-time if a scalar like $a holds a coderef, but doing the idempotent operation \& (that means reference of the dereference) makes it undoubtable for the parser.
UPDATE:
prototypes are a compile-time not run-time issue, which explains the confusion.
@JadeNB: defining your own prototype opens the oportunity to say sub f([&$]) { ... } such that at compiletime any scalar is acceptable, which can be assured at runtime to be a coderef!¹
UPDATE:
(1) Unfortunately this is wrong, there is no sub f([&$]) { ... } for an alternative first argument type, only sub f(\[&$]) { ... }. But this will NOT work like map() or grep(), because now the argument "absolutely must start with that character" (here & or $)
| [reply] [d/l] [select] |
|
| [reply] [d/l] [select] |
|
My objection to the above answer (to which the @-response above is directed) is that writing \&$a no more guarantees that $a is a coderef than just writing $a does.
Well you're right but from the perspective of the prototyped function it's clearly the goal to get a coderef at that point and nothing else.
It delegates the problem to the code-dereferencing in &$a.
so it's not anymore a problem of the implementation of prototypes but of the implementation of &-dereferencing.
And you will agree that it doesn't make sense in a loosely typed language like perl to guarantee that the scalar following & is a coderef!
Otherwise it would become in consequence necessarily a typed language!
UPDATE: To make it clearer "prototyping" is an option to make perl-functions more "strongly typed", and the function assures this "typing" within it's possibilities, it can't look ahead and alter the behaviour of &.
And & can't be strongly typed!
Hence you see problems only at run-time...
| [reply] [d/l] |
Re: coderefs and (&) prototypes
by JadeNB (Chaplain) on Jul 27, 2009 at 22:32 UTC
|
What confuses me is that, in Prototypes, it is suggested that a function prototyped (&@) behaves like grep. However, whereas one calls
grep &$code, @list
to find all the elements $_ of @list for which $code->($_) is true, for a hypothetical mygrep (&@), one would call
mygrep \&$code, @list
to achieve the same goal.
ikegami pointed out yet another subtlety, which is that grep cannot accept a ‘calculated’ first argument. Thus,
sub mygrep (&@) { my ( $code, @list ) = @_; return grep &$code, @list
+}
sub gt { my ( $test ) = @_; return sub { $_ > $test } }
grep &{ gt 1 }, qw/1 2 3 1 4/; => ()
mygrep \&{ gt 1 }, qw/1 2 3 1 4/; => ( 2, 3, 4 )
(While I'm at it, ikegami also pointed out that my first example could be written as grep $code->(), @list, which I find completely bizarre.)
UPDATE: Actually, having just run the code, it seems to me that the grep and mygrep invocations actually return the same thing (namely, (2, 3, 4)), so I must have misunderstood. Can anyone (like ikegami :-) ) clarify for me? | [reply] [d/l] [select] |
|
sub func {}
my $x; sub foo { ++$x }
$x = 0; func foo(), 1,2,3; print "$x\n"; # 1
$x = 0; grep foo(), 1,2,3; print "$x\n"; # 3
grep and map don't have a prototype since this ability can't be prototyped.
You seem to think there's something special about &cb when passed to grep. It's not.
grep &cb, ... === grep { &cb } ...
grep &cb(), ... === grep { &cb() } ...
grep $cb->(), ... === grep { $cb->() } ...
grep /.../, ... === grep { /.../ } ...
grep substr($_, 2), ... === grep { substr($_, 2) } ...
(minus the extra scope).
&cb means "call non-builtin cb without changing or localising @_", whether it's the first arg of grep or otherwise.
grep cannot accept a ‘calculated’ first argument. [...] Can anyone (like ikegami :-) ) clarify for me?
Using map since it's easier to visualize,
sub mymap (&@) { my ( $code, @list ) = @_; map &$code, @list }
sub get_cb { return sub { uc } }
print(( mymap \&{ &get_cb }, qw( a b c ) ), "\n");
print(( map &get_cb, qw( a b c ) ), "\n");
print(( map get_cb, qw( a b c ) ), "\n");
print(( map get_cb(), qw( a b c ) ), "\n");
ABC
CODE(0x1829a54)CODE(0x1829a54)CODE(0x1829a54)
CODE(0x1829a54)CODE(0x1829a54)CODE(0x1829a54)
CODE(0x1829a54)CODE(0x1829a54)CODE(0x1829a54)
Just can't do it. The first arg of map and grep is never evaluated before map or grep is called.
| [reply] [d/l] [select] |
|
print(( map &{ get_cb() }, qw/a b c/), "\n");
printed ABC, just as mymap did. Am I still missing something? | [reply] [d/l] [select] |
|
|
What confuses me is that, in Prototypes, it is suggested that a function prototyped (&@) behaves like grep.
The answer is simple! IMHO perlsub is wrong at this place and you proved it! 8)
Congratulations we found the second error in this perldoc chapter within two weeks!
Should be noted that CORE::map has no prototype to be copied, it has hidden magic behavior:
print undef==prototype "CORE::map" # prints 1
IMHO if there was a possibility to express the behavior with prototypes, perl would use it internally!
| [reply] [d/l] |
|
|