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

I've already sort of asked this in a previous thread (Using tie to return value of anonymous sub), but now I've figured out one of the real problems and simplified it a bit.

Basically I want to auto-evaluate anonymous subroutines on access and have it work whether or not arguments are passed to the routine. In other words, I need some way of figuring out how the return value of some FETCH sub is going to be evaluated -- not in void/scalar/array context, but if it's going to be dereferenced as a subroutine.

Example of what I'd like to do:

my $sub = sub { print "I see arguments\n" if @_; print "Hello\n"; }; # $sub might be in a hash somewhere print $sub; # I expect "Hello" print $sub->("foo", "bar"); # I should see "I see arguments" too.

What I'm using this for is attributes for objects. $enemy->{strength} could be 5 or a subroutine that factors in stuff dynamically. But when I access it (print "Watch out!\n" if $enemy->{strength} > $player->{strength}), I don't want to care about the implementation of strength. But sometimes I will want to pass in parameters. If I make a tie interface to return the return value of the code ref, then I lose the ability to pass in parameters.

I've sort of figured out the solution: Only tie certain 'layers' of my object's data structure. Things like the dynamic strength coderef wouldn't really need to take arguments anyway. So there really isn't a problem. But now it bugs me: There should be a way to make easy things easy and tough things possible.

Replies are listed 'Best First'.
Re: Auto-evaluating anonymous subroutines
by ikegami (Patriarch) on Feb 21, 2006 at 00:49 UTC
    I recommend against your plan -- it is unduly complex -- but it is possible using operator overloading.
    use strict; use warnings; package Character; use AutoExecute; sub new { my ($class) = @_; return bless({ poisoned => 0, }, $class); } sub poisoned { my ($self) = @_; return auto_execute { my ($poisoned) = @_; $self->{poisoned} = $poisoned if @_; return $self->{poisoned}; }; } package main; my $char = Character->new(); my $poisoned = $char->poisoned; print($poisoned, "\n"); # 0 $poisoned->(1); print($poisoned, "\n"); # 1 $poisoned->(0); print($poisoned, "\n"); # 0

    AutoExecute.pm: (Could probably use a better name)

    package AutoExecute; BEGIN { our @EXPORT = qw( auto_execute ); *import = \&Exporter::import; } use overload '""' => \&stringify, '&{}' => \&code_ref, ; sub auto_execute(&) { my ($sub) = @_; return bless(\$sub); } sub new { my ($class, $sub) = @_; return bless(\$sub, $class); } sub stringify { my ($self) = @_; my $sub = $$self; return $sub->(); } sub code_ref { my ($self) = @_; my $sub = $$self; return $sub; } 1;

    But all of the above would be much faster and just as neat if written as follows:

    use strict; use warnings; package Character; sub new { my ($class) = @_; return bless({ poisoned => 0, }, $class); } sub poisoned { my ($self, $poisoned) = @_; $self->{poisoned} = $poisoned if @_ >= 2; return $self->{poisoned}; } package main; my $char = Character->new(); print($char->poisoned, "\n"); # 0 $char->poisoned(1); print($char->poisoned, "\n"); # 1 $char->poisoned(0); print($char->poisoned, "\n"); # 0

      There's no need to put your @EXPORT and *import assignment in a BEGIN block. File-level code in modules is run just after it's been compiled. It's where the required "true" value comes from. That is, if Character uses AutoExecute, the work required to set up Exporter has already completed by the time AutoExecute->import() is run. You only need to use a BEGIN block if other code in the same module cares that your snippet runs before later code in the same file is compiled.

      ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

      ++ for the stringify use.

      I've not used the overload module before, and that was a nice introduction.

      It almost makes me feel Java-esque to realise I can have a toString routine in my objects!

      Steve
      --
Re: Auto-evaluating anonymous subroutines
by QM (Parson) on Feb 20, 2006 at 23:19 UTC
    Kibitzing without example code is a dangerous endeavor, but...what the heck.

    If you don't know if it's a sub, how do you know it takes arguments? This seems exactly why the method syntax is useful.

    More convoluted - arrange to pass arguments to everything. Have some wrapper or go-between sub check that the target is a sub or not, and invoke it with arguments if it is.

    Last time I checked, ref indicates this for you:

    my $x = sub {print "Hello\n"} print ref($x), "\n"; __OUTPUT__ CODE
    I could be off on a wild goose chase here, but it seems you've decided on the solution (tie) independent of your problem space. The suggestion in the other thread about method syntax still seems square on target.

    -QM
    --
    Quantum Mechanics: The dreams stuff is made of

Re: Auto-evaluating anonymous subroutines
by dragonchild (Archbishop) on Feb 21, 2006 at 01:18 UTC
    Despite the fact that using OO would make your life 10x easier, you're looking for Contextual::Return
Re: Auto-evaluating anonymous subroutines
by Roy Johnson (Monsignor) on Feb 21, 2006 at 15:28 UTC
    If I make a tie interface to return the return value of the code ref, then I lose the ability to pass in parameters.
    No you don't. You would pass them by assignment. Whether that makes sense as an idiom in your model is something you'll have to decide.
    #! perl use strict; use warnings; package Doohickey; require Tie::Scalar; our @ISA = 'Tie::StdScalar'; # returns the value you presumably want to print. sub FETCH { "Hi\n"; } # This actually prints a value, and then if you use the assignment, # the FETCH is called that returns a value sub STORE { my ($this, $val) = @_; print "I see arguments ($val)\n"; } package main; my $enemy; tie $enemy->{strength}, 'Doohickey'; printf "%s\n", $enemy->{strength} = 5; # you could "pass" a ref to an +argument list, too print "...\n"; print $enemy->{strength}, "\n";

    Caution: Contents may have been coded under pressure.