Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Re: Using tie to initialize large datastructures

by clemburg (Curate)
on Aug 08, 2001 at 17:18 UTC ( [id://103017]=note: print w/replies, xml ) Need Help??


in reply to Using tie to initialize large datastructures

Would something like this suite your needs (demo just for scalar variables)?

Approach: tie() builds up a mapping between tied objects and fully qualified subroutine names. When the tied object is asked for its value, we call the subroutine with the name passed when tie()ing the object. This subroutine caches the object's value.

Benefits:

  • Existing code can stay the same, you just need to tie() the global variables.
  • Existing init routines can be reused.
  • If you pass in a fully qualified name for a package global or a reference to a lexical variable as an additional argument to the FETCH method, the tie to this variable will be undone after first use. (Thanks to tilly for pointing out this works with lexicals, too!)

Update: I see you called for something more - untie()ing the object after first use. For lexical ("my") variables, I don't currently see how to do this, since we have no access to them inside the FETCH function (Ah, we *can* have that - thanks tilly again - see above). For true package globals, it's easy: just set the (in this case scalar) entry of the glob in the package you call the FETCH from to the new value and remove the tie(). Hm ... let's see if this works ... yup it does!

Demo code:

#!/usr/bin/perl -w use strict; # show how to tie scalars existing init routines the lazy way $| = 1; # -------------------------------------------------- package LegacyRoutines; use vars qw($AUTOLOAD); sub foo { print __PACKAGE__ . "::foo magically called\n"; return 42; } sub baz { print __PACKAGE__ . "::baz magically called\n"; return "hooray"; } # no bar routine here - catch errors sub AUTOLOAD { "LegacyRoutines: undefined subroutine $AUTOLOAD called\n"; } # -------------------------------------------------- package MyGlobals; # global to map objects to associated init routine names my %mappings; # global to memorize package globals to initialize my %vars; sub TIESCALAR { my $class = shift; my ($name, $var) = @_; bless \ (my $self), $class; $mappings{\$self} = $name; $vars{\$self} = $var; return \$self; } sub FETCH { print __PACKAGE__ ."::FETCH called\n"; # $_[0] - alias to original object ref we stored in %mappings my $value; if (not defined ${$_[0]}) { print "Initializing $_[0] ... \n"; # check if we have an entry for that object if (not exists $mappings{$_[0]}) { print "No matching subroutine for ", $_[0], "\n"; return $_[0]; } # call to init routine associated with $self no strict 'refs'; # set original value ${$_[0]} = &{ $mappings{$_[0]} }(); # remember it $value = ${$_[0]}; # untie package global if (exists $vars{$_[0]}) { untie ${$vars{$_[0]}}; } return $value; } return ${$_[0]}; } sub STORE { # whatever you want } sub DESTROY { # whatever you want } # -------------------------------------------------- package main; use vars qw($foo); tie($foo, "MyGlobals", "LegacyRoutines::foo", "main::foo"); tie(my $bar, "MyGlobals", "LegacyRoutines::bar"); tie(my $baz1, "MyGlobals", "LegacyRoutines::baz"); tie(my $baz2, "MyGlobals", "LegacyRoutines::baz"); # make $baz2 a de-facto alias to $baz1 print $foo, "\n"; print $foo, "\n"; print $bar, "\n"; print $baz1, "\n"; print $baz1, "\n"; print $baz2, "\n"; print $baz2, "\n";

Christian Lemburg
Brainbench MVP for Perl
http://www.brainbench.com

Replies are listed 'Best First'.
You can hack anything...
by tilly (Archbishop) on Aug 08, 2001 at 18:07 UTC
    Write an initialization function that accepts two arguments. References to the variable you wish to tie and the function you want to provide its initial value. The initialization function then does a tie of the variable which passes as one of the arguments a reference to the variable you are tying. Now you have access inside the FETCH routine to the untie logic.

    Note, though that I would avoid this solution. To me using so many globals that initializing them all takes too much memory is the real problem, and finding ways to enable that mistake to be extended is worse than fixing the mistake...

      I am definitively with you on the point of using so many globals. I would never consider to do something like this on many global variables just because of efficiency concerns. A design that needs such hacks is probably flawed.

      OTOH, I found this to be an interesting problem with respect to tie() usage.

      As for your suggestion - thanks! This really works. Funny. You can even use the code like it stands. Just pass in a ref to the lexical, and you're done. Like this:

      my $baz3; tie($baz3, "MyGlobals", "LegacyRoutines::baz", \$baz3); print $baz3, "\n"; print $baz3, "\n";

      Christian Lemburg
      Brainbench MVP for Perl
      http://www.brainbench.com

        But note that you are now responsible for manually syncronizing the variable you tie with the variable that gets untied. I would hide that with something like (untested):
        sub lazy_init { tie $_[0], "MyGlobals", $_[1], \($_[0]); } # Then elsewhere lazy_init(my $baz3, "LegacyRoutines::baz"); print $baz3, "\n"; print $baz3, "\n";
        If you are going to propagate bad, unmaintainable hacks, there is still no excuse to not try to identify and kill potential causes of error. With enough hacks you can even make it look halfway decent...

        Of course it isn't really halfway decent. For instance if you avoid using the variable, you will have a memory leak. One solution is to manually untie somewhere. Of course that is something that can easily go wrong. With 5.6.x you can download and install WeakRef and use that to break the self-reference. For older versions of Perl you would need to untie. The cleanest safe way to do that is to return a ReleaseAction handle that will untie when it goes out of scope. Or you can play with the reference counts directly at the C level.

        With all of that, I think you get a reliable implementation of this feature. I still don't like it though.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (None)
    As of 2024-04-25 01:31 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?

      No recent polls found