Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

coderefs and (&) prototypes

by LanX (Saint)
on Jul 27, 2009 at 22:21 UTC ( #783674=perlquestion: print w/replies, xml ) Need Help??

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)

Cheers Rolf

UPDATE: Thanks JadeNB nice problem, I learned a lot! 8)

Replies are listed 'Best First'.
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 (\%)).

      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?

        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

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!¹

    Cheers Rolf

    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 $)

      LanX suggested moving the CB discussion to a more persistent medium, so here we are. My objection to the above answer (at which the @-response was directed) was that writing \&$a no more guarantees that $a is a coderef than just writing $a does. (It maybe indicates more strongly that we think that $a is a coderef, but, if our thoughts about programs were always correct, then we wouldn't need compile-, or even run-, time checking.) We still get a run-time error if $a is not a coderef.

      Defining one's own prototype is a great solution, I agree, but doesn't help if (somewhat ironically) I'm trying to use Scalar::Util::set_prototype to set the prototype of a named coderef. :-)

        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!

        Cheers Rolf

        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...

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?

      The execution of the argument passed to grep is deferred until after grep is called, and it is called once for every following argument.

      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.

        Thanks very much for the clarification. I keep forgetting that the sub passed to map operates on $_ rather than receiving its argument in @_, so I'm always writing something like map &f, qw/1 2 3/ and wondering why I don't get (f(1), f(2), f(3)).

        For your second example, though, when I tested it, the code

        print(( map &{ get_cb() }, qw/a b c/), "\n");
        printed ABC, just as mymap did. Am I still missing something?

      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!

      Cheers Rolf

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://783674]
Approved by moritz
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others meditating upon the Monastery: (1)
As of 2023-09-21 22:28 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?