Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Passing an anonymous block to a method.

by BrowserUk (Patriarch)
on Mar 10, 2011 at 15:30 UTC ( [id://892443]=perlquestion: print w/replies, xml ) Need Help??

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

Am I right that there is no clean way to pass an anonymous block to a method?

Basically, it would be really convenient if this worked:

package something; sub new{ bless {}, __PACKAGE__ } sub doit ($&$) { local $_ = $_[0]->{ $_[2] }; $_[1]->(); } ... package main; my $o = something->new; $o->doit{ $_ += 1 }, 'fred';

Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.

Replies are listed 'Best First'.
Re: Passing an anonymous block to a method.
by wind (Priest) on Mar 10, 2011 at 17:05 UTC
    The following works, and arguably feels cleaner than a prototyped method would assuming those worked too:
    package something; sub new{ bless {}, __PACKAGE__ } sub doit { my ($self, $subref, $key) = @_; local $_ = $self->{ $key }; $subref->(); $self->{ $key } = $_; } package main; my $o = something->new; $o->doit(sub { $_ += 1 }, 'fred'); print "'$o->{fred}'\n"; # Outputs 1

    Update: Simplify code per AnomalousMonk's recommend.

      I don't understand the comment
          # Prevent $subref from playing with vars
      which seems to refer to the lexical variable definition
          my ($self, $key);
      which follows it and effectively masks lexicals of the same name defined in a superior scope. Can you please explain further?

        AnomalousMonk,

        You are correct. I was mistakenly being overprotective since I was treating an anonymous sub invocation like I was doing an eval. There is no need to worry about scoping of anything except global variables when calling the anon sub. Thank you for making me rethink this.

        Nevertheless, if this was anything more than an example, I would also redesign the method so that it doesn't require an explicit assignment to $_ within the anonymous sub but instead does that assignment for you.

        $self->{ $key } = $subref->();

        I chose to add parameter variables as a way of making the method self-documenting, but I didn't want the anonymous subroutine to have access to the deeper mechanisms of the object. Ideally, the only variable within the anonymous sub will be $_. However, to prevent a user from taking advantage of the lexicals $self and $key, I explicitly hid them.

        If this was anything more than an example, I probably would've localized @_. But then again, I would also redesign the method so that it doesn't require an explicit assignment to $_ within the anonymous sub, but instead does that assignment for you.

        $self->{ $key } = $subref->();
      ... to prevent a user from taking advantage of the lexicals $self and $key, I explicitly hid them.

      But  $self and  $key are lexicals defined within the scope of the  doit subroutine (in the example given in Re: Passing an anonymous block to a method.) and so could not possibly (without resort to something like PadWalker*) be accessed by any code defined in a block outside of  doit such as the one used to create the subroutine reference in the example. Am I missing something?

      * Actually, AFAIU PadWalker, the lexicals  $self and  $key of  doit would still not be accessible via PadWalker because  doit is not called from the anonymous subroutine, nor are those lexicals in scope at the time the anonymous code executes. Update: On second thought, those lexicals would be accessible because the anonymous subroutine is called from  doit and the lexicals are in scope at the time of the call; further, they would be accessible even in the presence of masking lexicals however, experiment shows they would not be accessible in the presence of masking lexicals.

      Update: Oops: All this mess was actually intended to be a reply to Re^3: Passing an anonymous block to a method..

Re: Passing an anonymous block to a method.
by Aaronrp (Scribe) on Mar 10, 2011 at 15:48 UTC

    Are you just trying to avoid the 'sub' keyword?

    $obj->method(sub { do something } ) ;

    should work, I think.

    -- Am I the only one who still cares about RT #67694?
      Are you just trying to avoid the 'sub' keyword?

      Effectively yes.

      For the same reasons as 99% of map and grep uses are coded as

      ... = map{ ... } ...

      rather than

      ... = map( sub{ ... }, ... );

      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Passing an anonymous block to a method.
by kennethk (Abbot) on Mar 10, 2011 at 15:35 UTC
    According to Prototypes in perlsub it's possible:
    sub mygrep (&@) mygrep { /foo/ } $a, $b, $c

    However, I seem to remember a thread a few months back where a monk said it was not worth the hassle. Example lifted from the docs:

    #!/usr/bin/perl use strict; use warnings; sub mygrep (&@) { my $code = shift; my @result; foreach $_ (@_) { push(@result, $_) if &$code; } @result; } print join "\n", mygrep { /a/ } qw(cat hat bed den);

    Update: I did some searching , but did not uncover the article I was thinking of. I did come across some thoughts from GrandFather, but they did not contain a compelling argument, at least to my eyes.

    Update 2: And disregard the above. Somehow I hadn't processed the word "method" from the above.

      Unfortunately not, for two reasons:

      • Prototypes are disregarded when a code block is invoked as a method (see perlsub).
      • & as a prototype only works if it is in the first position. $& does not allow to leave the sub from the second parameter.

      Unfortunately (but very usefully), that is being called as a subroutine, not a method. And method syntax doesn't respect prototypes :(


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Passing an anonymous block to a method.
by TGI (Parson) on Mar 11, 2011 at 16:25 UTC

    Since prototypes are compile time effects and method resolution happens at run time there is no (easy) way to make them happy together. You could do something with a sub based wrapper:

    sub do_this (&$$;@) { my $code = shift; my $method = shift; my $obj = shift; $obj->$method( $code, @_ ); } do_this {$_ += 1} do_it => $o, 'fred';

    Yep, it's all backwards order-wise. By using multiple prototyped subs you might be able to make a saner syntax. Something like this: run_code { 'foo' } on_object $o  with_method 'bar' and_args 'fred', but less stupid.


    TGI says moo

      Thank you for looking at this from another perspective.

      The more I investigate this, the more I'm convinced that the source of the problem is my having bought into the monoculture of OO syntax, whilst not wanting to give up the convenience and flexibility of being able to tailor the syntax to the task.

      Once that realisation dawned, it looked more closely at whether I was actually benefiting elsewhere from OO for this particular application, and I've pretty much concluded no.

      Since then, I've made some progress. My ideal situation would be to use an lvalue accessor to the properties and that seemed ideal until I discovered that the actual assignment to the underlying value accessed via an lvalue sub is done outside of the scope of that sub.

      That is, in the following:

      my $x; sub x :lvalue { use somepragma; $x; } x() += 1;

      The assignment of 1 to $x is not subject to somepragma. And that was the purpose of using an accessor in the first place.

      Dropping the OO and using the anonymous block syntax seems to be my best bet at this time. Now I'm investigating appying the :shared attribute to an entire namespace.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (None)
    As of 2024-04-25 00:00 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?

      No recent polls found