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.
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. | [reply] [d/l] |
|
| [reply] [d/l] [select] |
|
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->();
| [reply] [d/l] [select] |
|
... 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..
| [reply] [d/l] [select] |
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?
| [reply] [d/l] |
|
... = 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.
| [reply] [d/l] [select] |
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. | [reply] [d/l] |
|
| [reply] [d/l] [select] |
|
| [reply] |
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.
| [reply] [d/l] [select] |
|
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.
| [reply] [d/l] [select] |
|
|