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

I have a class with MANY internal variables, and I am trying to write a single get method that will access any one of them, where I pass in a string that contains the variable name(security is not an issue). Basically, I am trying to be lazy. My broader goal is to encapsulate the parsing of an external file so that when the format of that file changes, I only have to change the parser and not other parts of my code. I have been unable to get the evaluation to work. My NON-WORKING code looks like this:

package parser; my $var1; my $var2; my $var3; ... #set values -- this part works sub parse{ ... $self->{$var1} = 'a'; $self->{$var2} = 'b'; $self->{$var3} = 'c'; ... } #get values -- doesn't work. $var_name is a string such as 'var1', # so would be called as get_var('var1'). My goal is to get # back the value of $var1 sub get_var{ my ($self, $var_name) = @_; return $self->{$$var}; #also tried double quotes, etc. }

I would be very grateful for enlightenment

Replies are listed 'Best First'.
Re: trouble with evaluate
by Eliya (Vicar) on Apr 08, 2011 at 19:10 UTC

    Not sure I understand what you're trying to do.  Do you want to retrieve the value of my $var1 or the value of $self->{$var1} (i.e. where the attribute name is in the value of the class variable $var1) ?

    The first could be achieved like this

    sub get_var{ my ($self, $var_name) = @_; return eval "\$$var_name"; }

    the second like so

    sub get_var{ my ($self, $var_name) = @_; return $self->{eval "\$$var_name"}; }

    (As both are methods, you'd need to call them as $val = $obj->get_var('var1').)

    Update: here's a full example, so you know how I interpreted your intentions... :)

    #!/usr/bin/perl -wl use strict; package parser; my $var1 = "foo"; sub new { my $self = bless {}, $_[0]; $self->parse(); return $self; } sub parse{ my $self = shift; $self->{$var1} = 'bar'; } sub get_class_var{ my ($self, $var_name) = @_; return eval "\$$var_name"; } sub get_inst_var{ my ($self, $var_name) = @_; return $self->{eval "\$$var_name"}; } package main; my $p = parser->new(); print $p->get_class_var('var1'); # -> foo print $p->get_inst_var( 'var1'); # -> bar

      Thank you for your help, Eliya . Your comment about class variables vs. instance variables made me realize I was confusing the two. My goal is to create an object that does some work (parses a file) and then persists while other things are happening, and can be queried to get values of any of 50-100 different values that it holds in instance variables. Does that mean that all of those variables should be in the constructor? I tried both versions of the code you suggested, but they both return "undefined" for $var1, so either the eval {} syntax is not quite right, or $var1 isn't getting set correctly. I'm still trying to figure it out.

        can be queried to get values of any of 50-100 different values that it holds

        I think what you want is actually rather simple.  The classic way to store instance data is in the hash that makes up the object ($self here). In other words, as the "variable names" are just keys in the hash (i.e. strings), there's no need for eval or anything like this.  Your envisaged generic accessor (get_var) could just do a simple hash lookup:

        ackage parser; sub new { my $self = bless {}, $_[0]; $self->parse(); return $self; } sub parse{ my $self = shift; $self->{var1} = 'foo'; $self->{var2} = 'bar'; #... } sub get_var{ my ($self, $var_name) = @_; return $self->{$var_name}; } package main; my $p = parser->new(); print $p->get_var('var1'); # -> foo print $p->get_var('var2'); # -> bar

        (note that ->parse() doesn't necessarily have to be called from the constructor)

        Does that mean that all of those variables should be in the constructor?
        No.
Re: trouble with evaluate
by GrandFather (Saint) on Apr 09, 2011 at 02:44 UTC

    On the face of it what you want to do is trivial. Consider the following:

    use strict; use warnings; package Parser; sub new { my ($class, %params) = @_; return bless \%params, $class; } sub parse { my ($self, $inFile) = @_; while (<$inFile>) { chomp; my ($var, $value) = split; $self->{$var} = $value; } } sub getVar { my ($self, $varName) = @_; return $self->haveVar ($varName) ? $self->{$varName} : undef; } sub haveVar { my ($self, $varName) = @_; return exists $self->{$varName}; } package main; my $parser = Parser->new (); $parser->parse (*DATA); print "var1 = ", $parser->getVar ('var1') if $parser->haveVar ('var1') +; __DATA__ var1 wibble var2 plonk var4 dibbly

    Prints:

    var1 = wibble
    True laziness is hard work

      Thank you! You guys are really amazing. I think your approach is basically the same as what sundialsvc4 was suggesting. I'm still struggling to grasp how Perl objects work. JavaFan wrote "A Perl object is a reference that's blessed into a package", and if I understand your code right, the reference in this case is to the hash that contains the parameters ("bless \%params, $class"). I agree that what I'm trying to do is trivial, but I am none the wiser about why none of the other versions suggested worked, except that the evaluation of the passed variable name was problematic.

        Perl objects are a reference to something (hash, scalar, array, code, ... - it doesn't much matter what) with a little magic stirred in. See Not quite an OO tutorial for a light weight introduction to Perl OO.

        The trap in Perl is that, without strictures (use strict; use warnings; - which you should always use btw.) you can do some pretty hairy stuff that looks like it might be fine and just what you want to do, but is really full of nasty lurking traps. The nasty thing that most often looks ok is called "symbolic references" where you use the contents of a variable to access another variable by name. Pretty much always you can replace the referenced variable by a hash element where the variable name is the key to the hash.

        For the task at hand it is convenient to turn the hash into an object that knows how to parse an input file (or whatever) and provides convenient access to the parsed variables. This is a pretty light weight use of OO, but it is likely to provide significant payback if you need to add value to the data, like combining the values of actual variables into pseudo variables which can be accessed just like the actual variables.

        True laziness is hard work
        ... I am none the wiser about why none of the other versions suggested worked ...

        genghis: You might get even more and better help and make more headway if, when you say something like "it didn't work", you would also give the other monks a small, self-contained, runnable example of the "it" in question, along with input and expected output. The very process of putting together such an example can often provide you great enlightenment.

Re: trouble with evaluate
by wind (Priest) on Apr 08, 2011 at 18:41 UTC

    Assuming you have use strict on like you should...

    Then the following would allow you to access package variables declared with our (not with my).

    our $var1; our $var2; sub get_var{ my ($self, $var_name) = @_; no strict 'refs'; return ${"$var_name"}; }

    I'm really not sure why you would want to do that though.

      "Thanks wind, but I was trying to avoid "our" because of all of the dire warnings I have read about using it."

        What "dire warnings" about using our?

        Also, you claim that the below parse function works. How does it actually "work"? What do you believe that you're saving in $self->{$var1} and how are you planning on using it?

        my $var1; my $var2; my $var3; #set values -- this part works sub parse{ ... $self->{$var1} = 'a'; $self->{$var2} = 'b'; $self->{$var3} = 'c'; ... }

        If you're doing true Object Oriented programming, I believe that what you truly want is just the following

        package parser; ... # Some constructor here ... #set values -- this part works sub parse{ ... $self->{var1} = 'a'; $self->{var2} = 'b'; $self->{var3} = 'c'; ... } sub get_var{ my ($self, $var_name) = @_; return $self->{$var_name}; }
Re: trouble with evaluate
by locked_user sundialsvc4 (Abbot) on Apr 08, 2011 at 23:22 UTC

    Basically, it sounds like you’re asking for a slight twist on “accessors.”   A Perl object is basically just a hash.   You can, therefore, obtain values from it that way.   (And yes, as you plan to do, “the object itself” should be the one that knows how to do this, and who checks for incorrect or nonsensical requests.)

    Lately, I’ve gotten into the habit of putting “variables like these” into a separate hash, which is part of the object instance and with a name like, say, localvars.   These (and only these) are the values which are supposed to be accessible to routines like the ones you describe, and, since they’re now neatly in just one place, it’s easy to check them (and to Data::Dumper dump them).   It makes it really obvious that “you’re not supposed to just be grabbin’ any-old-key ... only one of these.”   And it’s easy to (Carp) confess if the name is wrong.

    $self->{'localvars'}->{$var_name} ...

      A Perl object is basically just a hash.
      No, it's not. A Perl object is a reference that's blessed into a package. A hash isn't a reference. And while it's true many Perl objects are hashreferences, that's not the only way to create an object.