Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

reference to an undefined key

by exilepanda (Pilgrim)
on Oct 10, 2015 at 12:20 UTC ( #1144368=perlquestion: print w/replies, xml ) Need Help??

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

Hello monks,

Sorry I believe I have given a pretty poor title but I really can't think of any better one, and I think this is the fact though. I'd like to know if there's any chance I can have such code candies ?

my $obj = bless { Root => $rootDir, UserDir => $obj->{Root} . "Usr/$userId/", UserAppData = $obj->{UserDir} . "$appName/", }, shift; # of cause this won't work
instead of :
$obj = bless { Root => $rootDir, UserDir => undef, UserAppData => undef, }, shift; $obj->{UserDir} = $obj->{Root} . "Usr/$userId"; $obj->{UserAppData} = $obj->{UserDir} . "$appName";
In fact I have much more paths that I need to defined while the object is created, but I hope to state the logic of the properties in clear at the first place.

I guess I am out of luck but still trying to ask because when the interpreter read {Root}, that should be some place already stored the value ( but I juz don't know how to ref to it), no?

UPDATE:

I know why it's not working, what I try to emphasize is only inside the hash, so please forgive I didn't manage to give good code about the before and after.

Though, I suddenly recalled something ( thank you very much for all you guys' feedback ). Yrs ago, I wrote a module for interpret an INI config file. Which accepts things like this:

[ConfigDirs] A = /some/dir B = /_#A#_/DeeperDir C = /_#B#_/EvenDeeperDir
And my module will go through it line by line. Every key recorded can be used by the next line. Regex will replace the _#*#_ with a proper recorded key's value. So it Data::Dump will produce something like
$var = { 'ConfigDirs' => { 'A'=> '/some/dir', 'B'=> '/some/dir/DeeperDir', 'C'=> '/some/dir/DeeperDir/EvenDeeperDir' } }
Now the questions can be added are: 1) is there any existing module deal's something like that? 2) is there any threat I elaborating things in this way?

As I am going to reuse this module's code to let me accept params as I originally proposed.

Replies are listed 'Best First'.
Re: reference to an undefined key
by golux (Chaplain) on Oct 10, 2015 at 14:04 UTC
    Hi exilepanda,

    You can't do it the way you're trying, because during the creation of the object "$obj" your code is referring to "$obj" itself which doesn't exist yet! That's kind of like trying to define a word by using the same word in its definition:

    "A recursive algorithm is an algorithm which recursively calls itself +to produce an answer"

    You have a couple of choices. The first is to use the second snippet of code you showed (which you already know works, since it waits until the object $obj is fully defined before trying to reference its data).

    Or you could construct the object without referencing the object itself:

    use strict; use warnings; package MakeObject; use Data::Dumper; my $rootDir = '/home'; my $userId = 'smith'; my $appName = 'project'; my $obj = MakeObject->new; printf "Object contents => %s\n", Dumper($obj); sub new { my $obj = bless { Root => $rootDir, UserDir => "$rootDir/Usr/$userId/", UserAppData => "$rootDir/Usr/$userId/$appName/", }, shift; return $obj; } __END__ Output is: Object contents => $VAR1 = bless( { 'Root' => '/home', 'UserDir' => '/home/Usr/smith/', 'UserAppData' => '/home/Usr/smith/project/' }, 'MakeObject' );

    Since the latter has the same effect -- it only refers to data which is already defined ($rootDir, $userId and $appName, but not $obj) -- it doesn't create the catch 22 effect of the original code.

    say  substr+lc crypt(qw $i3 SI$),4,5
      Thanks golux! your second opt is what I hope not to use as it can come really lengthy and will be my disaster if something is going to change ( eg. /Usr/ needed to change as /Users/ )... But what you propose remind me something I did yrs ago, do you have any feedback to my update on the post ?
        You're asking if there's any module for dealing with resolving templates. I'm sure there are, but none offhand that I've used extensively. (Did you try a search of cpan?)

        As to the inherent dangers of elaborating things as you suggest, the one that comes to mind is -- you'd better only make use of key/value pairs in the order they're resolved, or you may come up against infinite recursion. For example:

        [ConfigDirs] A = /some/dir B = /_#C#_/DeeperDir C = /_#B#_/EvenDeeperDir

        which would never fully expand (B references C, which references B again). So you wouldn't want to necessarily make that into a hash; a list would be more appropriate I think, as you could throw an error any time you came across a variable which hadn't previously been defined.

        Here's an example that uses an array (hardwired, but you could read it in from a config file), and then creates a hash of resolved parameters:

        #!/usr/bin/perl ############### ## Libraries ## ############### use strict; use warnings; use feature qw{ say }; ################## ## User-defined ## ################## # Define what makes a "template" variable my $re_template = qr/_#([^#]+)#_/; # The result of what you read from your config file, perhaps. # Note that it's an ARRAY, since we'd like to prevent unintentional # recursion (we should resolve template variables in the order in # which they're seen). my $a_params = [ [ 'A' => '/some/dir' ], [ 'B' => '/_#A#_/DeeperDir' ], [ 'C' => '/_#B#_/EvenDeeperDir' ], ]; ################## ## Main program ## ################## show_params($a_params, "[Before resolution]"); my $h_resolved = resolve_templates($a_params); show_params($h_resolved, "[After resolution]"); ################# ## Subroutines ## ################# sub resolve_templates { my ($a_params) = @_; my $h_resolved = { }; for (my $i = 0; $i < @$a_params; $i++) { my $idx = $i + 1; my $a_keyval = $a_params->[$i]; my ($key, $tmpl) = @$a_keyval; my $orig = $tmpl; while ($tmpl =~ /$re_template/) { my $ref = $1; my $refval = $h_resolved->{$ref}; defined($refval) or die "Undefined ref '$ref' in '$orig'\n +"; $tmpl =~ s/$re_template/$refval/g; } $h_resolved->{$key} = $tmpl; } return $h_resolved; } sub show_params { my ($a_params, $msg) = @_; $msg ||= "Parameters:"; say "-" x 79; say $msg; say "-" x 79; if (ref $a_params eq 'HASH') { # If a HASH was passed, turn it into an ARRAY my $h_params = $a_params; my @keys = sort { $a cmp $b } keys %$h_params; $a_params = [ map { [ $_ => $h_params->{$_} ] } @keys ]; } foreach my $a_keyval (@$a_params) { my ($key, $val) = @$a_keyval; my $text = "Parameter '$key' "; $text .= "." x (32 - length($text)); $text .= " '$val'"; say $text; } say ""; }

        And here its output:

        ---------------------------------------------------------------------- +--------- [Before resolution] ---------------------------------------------------------------------- +--------- Parameter 'A' .................. '/some/dir' Parameter 'B' .................. '/_#A#_/DeeperDir' Parameter 'C' .................. '/_#B#_/EvenDeeperDir' ---------------------------------------------------------------------- +--------- [After resolution] ---------------------------------------------------------------------- +--------- Parameter 'A' .................. '/some/dir' Parameter 'B' .................. '//some/dir/DeeperDir' Parameter 'C' .................. '///some/dir/DeeperDir/EvenDeeperDir'

        say  substr+lc crypt(qw $i3 SI$),4,5
Re: reference to an undefined key
by stevieb (Canon) on Oct 10, 2015 at 14:31 UTC

    You can't access attributes of an object before the object is created. It's like asking what the value of your bank account is, when you haven't even opened up an account yet.

    You don't show where the $userId or $appName are coming from, but I suspect you'll be accepting them as params in your new() method.

    What I would do here, is something like this, using an _init() method to do the lifting (untested). Note I've replaced $obj with $self, and extracted the variables for clarity, instead of just passing @_ around:

    Update: The first half of my example doesn't perform properly. See AnomalousMonk's post below as to how it's broken. I've left it in as to not break context. Their reply has a fix.

    use warnings; use strict; use Data::Dumper; package Test; sub new { my $self = bless {}, shift; my ($uid, $root, $app) = @_; my %attrs = $self->_init($uid, $root, $app); $self = \%attrs; return $self; } sub _init { my $self = shift; my ($uid, $root, $app) = @_; my %attrs; $attrs{Root} = $root; $attrs{UserDir} = $attrs{Root} . "Usr/$uid/"; $attrs{UserAppData} = $attrs{UserDir} . "$app", return %attrs; } package main; my $test = Test->new('steve', '/home/', 'perl'); print Dumper $test;

    If you do want to do all the work in the constructor method, you can create the object up-front, then access all of its attributes before returning it:

    sub new { my $obj = bless {}, shift; my ($uid, $root, $app) = @_; $obj->{Root} = $root; $obj->{UserDir} = $obj->{Root} . "Usr/$uid/"; $obj->{UserAppData} = $obj->{UserDir} . "$app", return $obj; }

    -stevieb

          sub new {
              my $self = bless {}, shift;
              ...
              my %attrs = $self->_init($uid, $root, $app);
              $self = \%attrs;
              return $self;
          }

          sub _init {
              ...
              my %attrs;
              ...
              return %attrs;
          }

      Unfortunately, overwriting the blessed reference to an anonymous hash  $self with an unblessed reference to the  %attrs hash destroys the blessing an object reference needs. Your original code produces

      $VAR1 = { 'Root' => '/home/', 'UserDir' => '/home/Usr/steve/', 'UserAppData' => '/home/Usr/steve/perl' };
      from the  print Dumper $test; statement. (Update: The second definition of  new() returns a properly blessed object.)

      A minimal change to produce a properly blessed object reference might be something like

      c:\@Work\Perl>perl -e "use warnings; use strict; ;; use Data::Dumper; ;; package Test; ;; sub new { my $self = bless {}, shift; my ($uid, $root, $app) = @_; ;; return $self->_init($uid, $root, $app); ;; } ;; sub _init { my $self = shift; my ($uid, $root, $app) = @_; ;; $self->{Root} = $root; $self->{UserDir} = $self->{Root} . qq{Usr/$uid/}; $self->{UserAppData} = $self->{UserDir} . $app, ;; return $self; } ;; package main; ;; my $test = Test->new('steve', '/home/', 'perl'); print Dumper $test; " $VAR1 = bless( { 'Root' => '/home/', 'UserDir' => '/home/Usr/steve/', 'UserAppData' => '/home/Usr/steve/perl' }, 'Test' );
      (However, I find the whole  my $obj = bless { What => 'ever' }, shift; syntax a bit funky if the original goal is "to state the logic of the properties in clear." But that's another story...)


      Give a man a fish:  <%-{-{-{-<

        Nice catch, that's a big one I'm surprised I completely overlooked. Thanks :)

        Thanks for your reply and this reminded me something, do you have any feed back for my updates in the post ?
      You can't access attributes of an object before the object is created.
      Yes, I know why my example won't work... but I am juz looking for if any work around to declare and define my object's properties in the style alike what I hope can happen.
      you can create the object up-front, then access all of its attributes before returning it...
      That's what I know I can deal with it so far, as shown in my original post ..so sad! =(
        ... I know why my example won't work... but I am juz looking for if any work around to declare and define my object's properties in the style alike what I hope can happen.

        The Monastery may harbor certain subtle and puissant monks who possess the dark art of accessing an object before that object is completely defined, but their ways should not be yours if you seek the Way of Clarity!

        If you wish "to state the logic of the properties in clear", I don't see why, combining the approaches of golux and stevieb with my own personal preferences, an approach like

        sub new { my $class = shift; my ($uid, $root, $app) = @_; my $obj = bless {} => $class; $obj->{Root} = $root; $obj->{UserDir} = "$obj->{Root}/Usr/$uid/"; $obj->{UserAppData} = "$obj->{UserDir}$app"; return $obj; }
        or maybe
        sub new { my $class = shift; my ($uid, $root, $app) = @_; return bless { Root => $root, UserDir => "$root/Usr/$uid/", UserAppData => "$root/Usr/$uid/$app", } => $class; }
        (both tested) would not be sufficiently clear. (Note the trailing / path separator is not needed on the  $root path specification.) Why be sad? Bask in the light of the Clear Path!

        Update: Just to be sure:


        Give a man a fish:  <%-{-{-{-<

        I agree completely with AnomalousMonk's post here: Re^3: reference to an undefined key. There *are* ways to do what you want (and we could definitely point them out), but it would involve using magic that isn't advised for standard use cases.

        Trying things out is one thing, but for code you're going to use, it's best to use idiomatic Perl that you, and anyone in the future can read easily at a glance six months after it is written.

        If you want to force your will upon a data structure creation, you'll want to look at symobolic references, and understand the symbol table. There's a decent intro here on PM: Of Symbol Tables and Globs.

        Again though, using magic to do things in a non-conventional way often leads to bugs that are "far away" or "at a distance" from the code that uses said magic, while leaving code that yourself, or others might not be able to comprehend in the future (even after a lot of review and tracing).

        With all those warnings stated, we don't learn how to force perl to do things unconventionally without pushing our limits of what we know.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1144368]
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (3)
As of 2020-10-31 23:30 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    My favourite web site is:












    Results (291 votes). Check out past polls.

    Notices?