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

(Hope this isn't too long) Ok, so I have this package:
#!/usr/local/bin perl -w use strict; package Yar; sub new { my $class = shift; my $self = {}; my %params = @_; foreach my $token (keys %params) { if (exists &$token) { $self->{uc $token} = $params{$token}; } else { warn "We don't have any \"$token\" thingies 'round these p +arts!\n"; } } bless($self); return $self; } sub name { my $self = shift; if (@_) {$self->{'NAME'} = shift;return 1} return $self->{'NAME'}; } sub age { my $self = shift; if (@_) {$self->{'AGE'} = shift;return 1} return $self->{'AGE'}; } return 1;
and this package.pl that calls it
#!/usr/local/bin/perl -w use strict; use Yar; my $yarobj = new Yar(name=>"YaR",age=>"23",foo=>"bar"); print "Output: ",$yarobj->name,$yarobj->age,"\n";
Which outputs exactly what it should, ie, it prints the warn line since we tried to add an arbitrary parameter to the new() call, and then prints the two parameters we set. What I'd like to know is can I replace the "$self->{uc $token} = $params{$token};" with an actual call to the subroutines in the package. I got various errors about stuff not being blessed, or other things I can't remember, and I was wondering if anyone had any input on how to do this, or if I should even bother.

Replies are listed 'Best First'.
Re: parsing parameters in a new() object call
by chromatic (Archbishop) on Jan 25, 2001 at 02:08 UTC
    You're on the right track. First, bless the object. Then, use the nifty can method:
    sub new { my $class = shift; my $self = {}; bless($self, $class); my %params = @_; foreach my $token (keys %params) { if (defined(my $sub = $self->can($token))) { $self->$sub($params{$token}); } else { warn "We don't have any \"$token\" thingies 'round these p +arts!\n"; } } return $self; }
    When I'm feeling this paranoid, I usually keep around a list of valid fields, but this is a good approach. The only drawback I see is that someone can call an arbitrary (non-accessor) method by passing the right parameters to the constructor.

    There are ways to get around that, too.

(tye)Re: parsing parameters in a new() object call
by tye (Sage) on Jan 25, 2001 at 00:43 UTC

    Change your code to:

    if (defined &$token) { no strict 'refs'; &$token( $self, $params{$token} ); }
    There is also a trick for doing this without the no strict 'refs' part (that was posted here recently), but I didn't memorize that as I figured the inconsistancy would be fixed one way or another at some point and I don't mind very small patches of no strict when needed.

    Updated slightly. chromatic's suggestion is better.

            - tye (but my friends call me "Tye")
Re: parsing parameters in a new() object call
by goldclaw (Scribe) on Jan 25, 2001 at 00:21 UTC
    There should be plenty of docs that explain this to you, but heres basically what you start a "typical" new with:
    sub new{ my $class=shift; $class=ref($class)||$class; my $self= bless {}, $class; #self is now a "proper object". You can even subclass #this, and it will work correctly... #You may now call object methods, or set things in the #hash referenced by $self: $self->{state}="hungry" $self->feed(); return $self }
    GoldClaw
Re: parsing parameters in a new() object call
by mr.nick (Chaplain) on Jan 25, 2001 at 00:21 UTC
    Hm, I couldn't get your example to work. The line
    if (exists &$token)
    fails on my system with a exists operator argument is not a HASH element at Yar.pm line 15..

    According to the perldocs, exists only takes a hash key as an argument.

    I'm not sure how you can go about testing for the existance of a subroutine short of something like

    eval "function()"; die "That function doesn't exist!" if $@;
    Which does work.
      (fixed some ugly formatting)

      From perl -V on my system: "This is perl, v5.6.0 built for MSWin32-x86-multi-thread".

      And from my man pages:

      exists EXPR
      
          ...  (EDIT: stuff about hash, then)
      
      Given an expression that specifies the name of a subroutine, 
      returns true if the specified subroutine has ever been declared,
      even if it is undefined.  Mentioning a subroutine name for 
      exists or defined does not count as declaring it.
      
               print "Exists\n"    if exists &subroutine;
               print "Defined\n"   if defined &subroutine;
      
      
      Maybe you have a different version of perl? Or maybe this is explicitly a win32 thing? I don't know myself. But I do know the code works on my machine :)

      AHA! it's an activestate thing at least: http://velocity.activestate.com/docs/ActivePerl/lib/Pod/perlfunc.html#item_exists
thanks guys
by YaRness (Initiate) on Jan 25, 2001 at 18:23 UTC
    goldclaw and chromatic nailed it. don't even ask me why i didn't try that myself, it just hadn't occurred to me yet.
    all of you seem to have either mentioned my use of "exists", either explicitly, or by replacing it with different code. is this just something new with 5.6, or what? i forget where i ran across the idea to use it, but it works just fine on my system... just looked at perldoc.org, and couldn't find this use of it defined. it's not documented on perl.com either. so i have no idea. but i swear by my bones that it compiles and runs!
      It is something new with 5.6:
      % perl -v This is perl, v5.6.0 built for i686-linux [snip] % perldoc perldelta [snip] exists() is supported on subroutine names The exists() builtin now works on subroutine names. A subroutine is considered to exist if it has been declared (even if implicitly). See the exists entry in the perlfunc manpage for examples. [snip]
      The perldelta documentation is the place to find out about significant changes in the latest version of perl. perldeltas from previous versions are also included in a new install, but renamed to include the version, e.g. perl5004delta.