Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Hiding, but maintaining variables in user-defined code

by Masem (Monsignor)
on May 23, 2001 at 06:45 UTC ( [id://82460]=perlquestion: print w/replies, xml ) Need Help??

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

This question is somewhat related to one I asked earlier, however, I don't think the same solution would be as effective here as elsewhere.

In this case, I have a class package; one of the functions of this class will take as an arguement user code. However, the user code may call functions that are part of this class package; these functions, unforunately, would require to know what the $self variable is as well as possibly other variables from the $self class. In the previous case, I decided on passing a hash containing variables that the user can have, but in this case, some of the variables should stay private, so giving them directly to the user is not a good idea.

I have no idea if it's possible to do what I want to do at all; I might have to play with globals at the package level to make it work right, but there's possibilities of running this in a threaded environment, which would make the use of such globals unreliable.

Here's some sample code to indicate what I'm aiming for. I want the helper() function to have a way to get $self from execute() as well as other variables without letting the user know about them in their coderef.

--in MyPackage.pm #!/usr/bin/perl -w package MyPackage; use strict; sub new { my ( $class ) = @_; my $self = {}; bless $self, $class; return $self; } sub execute { my $self = shift; my $coderef = shift; my $s = $self; return &$coderef( ); } sub helper { my $self = shift; print "in helper\n"; } 1; --in MyTest.pl #!/usr/bin/perl -w use strict; use MyPackage; my $test = MyPackage->new(); $test->execute( sub { print "in the local coderef\n"; $test->helper(); } );

Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain

Replies are listed 'Best First'.
Re: Hiding, but maintaining variables in user-defined code
by jeroenes (Priest) on May 23, 2001 at 10:08 UTC
    Masem, maybe I miss something here. But, by the very definition of OOP, the user already knows what $self is: It is the very object that (s)he is calling. In this case, $test. What else do you want?

    For globals and the like, just store everything in the $self={} hash, unless they are package globals. In either case, keep the interface clean by not letting the user access the $self hash and the globals directly. Use methods for that instead. So if you have a $self->{evil_case}->{unit666}=0, I would write a method $self->IsDevilPresent() that returns the zero. Your user code would than be:

    $test->execute( { print "This is user code\n"; die "The devil is in $test->name()! I can't live with that!" if $test->IsDevilPresent(); } );
    So in general, hide the implementation from the user. Keep the interface clean and the subroutines independent.

    Hope this helps,

    Jeroen
    "We are not alone"(FZ)
    Update: On the bike to work, my lightbulb went on (ask tilly about it ;-). You don't want to call the $test from the sub, but you want the dynamic $self from the package. So the code becomes:

    $test->execute( { my $self = shift; print "This is user code\n"; die "The devil is in $self->name()! I can't live with that!" if $self->IsDevilPresent(); } ); #In the package, you must supply $self as an arg: return &$coderef( $self );

      And what about allowing extra arguments for execute, simply by passing to the coderef whatever is left in @_?

      # in the class package sub execute { my $self = shift; my $coderef = shift; return &$coderef($self, @_); } # in the main $test->execute(sub { my $self = shift; # do something with the rest of @_ }, $arg1, $arg2);
      --bwana147
Re: Hiding, but maintaining variables in user-defined code
by Masem (Monsignor) on May 23, 2001 at 17:20 UTC
    Restating this, because there is a bit of confusion of what I want to do, and to put it more in context. Note that I'm thinking more about simplifying the end-user's side as opposed to simplicitiy in the module code at this point. Please note that this is for a personal project, so if it can't be done, I'll figure out something.

    An instance of MyPackage will have a function that can be used to pass in numerous coderefs defined by the user. The MyPackage instance will retain a list of these coderefs as an array, but this detail should be unknown to the user. When the user calls a second function on the instance of MyPackage (with no arguments), the function will select a certain coderef from the array (mostly at random), and execute that coderef. So far, so good; this is easy to do.

    However, I would like to provide some support functions that work on an instance of MyPackage, some which might need to know which coderef has been used. I know that since the user already knows the instance of MyPackage, they can use that to run this function, but the additional arguments to it can get problematic. From a UI point of view, they should not know about these extra arguments, only because if they call the functions with the wrong arguments, they may mess up the workings of the package.

    So conceptually, I'd like it to look like this:

    --in MyPackage.pm #!/usr/bin/perl -w package MyPackage; use strict; sub new { my ( $class ) = @_; my $self = {}; bless $self, $class; return $self; } sub execute { my $self = shift; my $coderef = shift; my $s = $self; my $extra_arg = $some_variable; return &$coderef( ); } sub helper { my $self = shift; my $extra_arg = shift; print "in helper\n"; } 1; --in MyTest.pl #!/usr/bin/perl -w use strict; use MyPackage; my $test = MyPackage->new(); $test->execute( sub { print "in the local coderef\n"; helper(); } ); # HERE'S THE RUB

    or, in other words, I want the call to "helper" in "execute()" to have arguements filled it for it without the user filling them in for himself, such that "helper" would be called with arguments ($self, $other_arg).

    Now that I write it down this way, I'm wondering if I can use @_ to make this work; I know that @_ behaves in some weird ways with subroutines, but I'll take a look at how that happens to see. But it should be noted that the user coderefs, from the user's standpoint, should not be expecting any arguments.

    The other option that I could go with is to use eval'd strings instead of coderefs; thus I could catch for the text "helper" and regex the variables that I need into it when the user sets this. The only thing I don't like about this is that you lose compile time syntax checking of the user's code.


    Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
      in other words, I want the call to "helper" in "execute()" to have arguements filled it for it without the user filling them in for himself, such that "helper" would be called with arguments ($self, $other_arg).

      This boils down to: MyPackage has to remember the arguments.

      Well, I would write a wrapper for that. Store the args in $self.

      sub execute{ $self = shift; $coderef = shift; if ( @_ ) { $self->args->{$coderef}=\@_ ; #or [$self, @_] }elsif ( ref(my $args = $self->args{$coderef}) eq 'ARRAY') { @_ = @$args; } return &$coderef( @_ ); }
      Hope this helps,

      Jeroen
      "We are not alone"(FZ)

      To follow up yet again, I did find something that might help, but will lead into a worse dead end.

      That is, if you call a function via the amperstand approach, with no arguements, the current value of @_ is passed along to that function. So in my case:

      $test->execute( sub { print "in the local coderef\n"; &MyPackage::helper; } );
      the value of @_ that helper() sees would be whatever execute() left @_ as, so here, I could easily fill up @_ with $self and any other variables that I wish. However... some of the functions of similar nature to helper that I have planned would also require user-added arguments, and thus this completely breaks down, since specifying any arguements does not pass @_ further.

      I think what I will end up doing is forcing the user to specify "$self->helper", and then remove any variables that would be passed to the help functions from access by the user.

      </CODE>


      Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
      package My; my $self; sub execute { #... $self=$call; $call->(); undef$self; }
Re: Hiding, but maintaining variables in user-defined code
by gumpu (Friar) on May 23, 2001 at 10:00 UTC

    Your example is a bit confusing to me. You call $test->helper() with the same $test as $test->execute. In that case is $self in helper not equal to $self in execute?

    Or do you mean that you want to call:

    $test->execute( sub { print "in the local coderef\n"; helper(); } );

    with

    sub helper { print "in helper\n"; }

    and still have access to $self in helper?

    Have Fun

Re: Hiding, but maintaining variables in user-defined code
by Vynce (Friar) on May 23, 2001 at 15:20 UTC

    i think it's unanimous -- we're confused by what it is you want. something i can't quite pin down suggests, though, that you might be helped by the fact that you can call

    $obj->method->othermethod

    if method above is something that returns another object. so if your first method returns an object with a particular relation (like, say, the parent or the first child or the appropriate filehandle) then you can say things like

    my @sibs = $obj->parent->list_children; my $file = $obj->io->get_final_location;

    etc. this means that knowing just $obj, you can get other relevant objects. but i'm not sure that's what you meant.

    you also might want to check out caller(), which can tell you what sub called the sub you're in, and further back than that. but if you want to be able to look at variables from caller's POV (like, say, Caller(1)::$self) then you're out of luck, i think. you'll just have to pass them along as arguments.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (4)
As of 2024-04-19 00:45 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found