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

Ok, so I am building a simple disk caching class to go out and record some directory snapshots for me. The reason being that it's quicker to look up an element in a multi-dimentional hash than it is to do a 'readdir(DIR)'. Anyway, I wanted to make it general so that I can add directories to the cache according to an alias; ie. the following code:
     use RadCache;
     my $cache = RadCache->new();
     $cache->AddAlias(myAlias => 'c:\temp');
Then I can do a build like so:
     $cache->Build();
However, I want to be able to add specialty build functions so you can do something like this:
     $cache->AddSpecialty(myAlias => \&myFunc);
Where &myFunc is written in the user's file, not the RadCache file. This has been R A D...

Replies are listed 'Best First'.
RE: Scoping and Objects
by radman (Novice) on Nov 16, 2000 at 00:07 UTC
    follow up... sorry didn't finish there and hit submit rather than preview. DOH! Ok, the problem I run into is scoping. If i refrence this specialty function like
         $cache->AddSpecialty(myAlias => \&myFunc);
    
    Then inside the object store the refrence in a hash like this:
         package RadCache;
         my %props = { SPECIALTY => {} };
         
         sub new {
              my $type = shift;
              my $self = {%props};
              return bless $self;
         }
    
    So that the AddSpecialty function just puts a new entry in
    %{$self->{SPECIALTY}}
    . But, when in the Build function, I want to call that specialty function, therein lies the problem. Is it solveable using an anonymous sub and passing that instead of a refrence to an already defined subroutine? Thanks for any help This has been R A D...
      From the standpoint of calling a subroutine by reference, there are no semantic or effective distinctions between whether it's a reference to a named subroutine or an anonymous subroutine.

      You can either prepend the sub ref with the ampersand, or use the dereferencing arrow and brackets. For example

      sub brag { print "I am so great, I even have a name.\n"; } my $not_famous = sub { print "I'm behind the scenes and forgotten.\n"; + }; my $famous = \&brag; # dereferencing with the & symbol &$not_famous; &$brag; # dereferencing with the arrow $not_famous->(); $brag->();
      Assuming you can get a CODE ref out of your data structure, call it the way you prefer.
        Maybe I didn't articulate well enough. I understand how to call the refrenced function, in fact I did that with a test. The problem is when that refrenced function, defined in a file separate and independant of the RadCache.pm file and stored as a refrence inside a property of the object, wants to act as a method of the object itself.
        Hmm...maybe I should just post my code...Here:
        package RadCache;
        
        use Carp;
        use vars qw($AUTOLOAD);
        
        my %properties = (NAME => "Default", DIRS => {}, SPECIAL_FNS => {}, CACHE => {}, DRIVERS => {});
        
        ##
        # Constructor
        # Takes any of the above properties as arguements in the form of PROPERTY => value.
        # Does not need any initial arguments
        # Returns a new Cache object
        ##
        sub new {
        	my $caller = shift;
        	my %args = @_;
        
        	print "Creating new cache...\n";
        	my $class = ref($caller) || $caller;
        	my $self = {
        		_permitted => \%properties,
        		%properties,
        	};
        	foreach my $item (keys %args) {
        		if (!($self->{_permitted}->{$item})) {
        			print "ERROR: $item is not a valid input argument. Ignoring...\n";
        			next;
        		} else {
        			$self->{$item} = $args{$item};
        		}
        	}
        	bless $self, $class;
        	print "COMMENT: Created Cache : " . $self->{NAME} . "\n";
        	return $self;
        }
        
        # CLASS METHODS
        --snip--
        # INSTANCE METHODS
        ##
        # LoadSpecialBuilder
        # Takes a list of builder functions specific to a named directory alias
        # used if you don't want the default builder function to run on your aliased directory
        # form is: $myCache->LoadSpecialBuilder(MYALIAS1 => \&specBFunc1, ..., MYALIASN => \&specBFuncN);
        # No Return Value
        # Requires that you have an ALIASED directory named in the DIRS hash.
        ##
        sub LoadSpecialBuilder {
        	my $self = shift;
        	my %args = @_;
        	unless (ref $self) {
        		croak "bad method call";
        	}
        	print "COMMENT: Loading Special Builder Function...\n";
        	foreach my $item (keys %args) {
        		$self->{SPECIAL_FNS}->{$item} = $args{$item};
        		print "COMMENT: Added special builder function for $item\n";
        	}
        }
        ##
        # UnLoadSpecialBuilder
        # Takes a list of aliases and removes them from the SpecialFunc hash in the Cache
        # NOTE: REMOVING A Special Function  MEANS reverting to the default builder function
        # form is: $myCache->UnLoadSpecialBuilder(MYALIAS1, MYALIAS2,...,MYALIASN);
        # No Return Value
        ##
        sub UnLoadSpecialBuilder {
        	my $self = shift;
        	my @args = @_;
        	unless (ref $self) {
        		croak "bad method call";
        	}
        	print "COMMENT: UnLoading Special Builder Function...\n";
        	foreach my $item (@args) {
        		if ($self->{SPECIAL_FNS}->{$item}) { # if the item is really in the DIRS alias hash, then delete it
        			delete($self->{SPECIAL_FNS}->{$item});
        			print "COMMENT: Special builder function for $item has been deleted\n";
        		} else {
        			print "ERROR: can't delete a non-existant alias!\n";
        			next;
        		}
        	}
        }
        ##
        # Build
        # Takes no arguments
        # Does the labor of calling special builder functions and for each non-special one
        #   does a default build
        # DEFAULT: just takes the first tier of element names from the ALIASED Directory if it is infact a directory
        # Fills in the CACHE property with the correct datastructure of :
        # 				ALIAS => STRUCTURE
        # STRUCTURE is dependant on the build. for DEFAULT build:
        #    			ALIAS => STRING
        # 				where STRING is a string of all the elements of a directory separated by a " "
        # No Return Value
        ##
        sub Build {
        	my $self = shift;
        	my @args = @_;
        	unless (ref $self) {
        		croak "bad method call";
        	}
        	print "COMMENT: Building the Cache...\n";
        
        	foreach my $dir_alias (keys(%{$self->{DIRS}})) {
        		if ($self->{SPECIAL_FNS}->{$dir_alias}) {
        			print "COMMENT: executing SPECIAL for $dir_alias...\n";
        			&{$self->{SPECIAL_FNS}->{$dir_alias}}();
        		}else{
        			$self->{CACHE}->{$dir_alias} = "";  # initialize a string for the cache
        			if (-d $self->{DIRS}->{$dir_alias}) {
        				# open the directory and then put all the element names on the cache string for lookup separated by a space
        				# THIS IS NOT GOOD, WHAT IF A FILENAME HAS A SPACE IN IT!
        				opendir(DIR, $self->{DIRS}->{$dir_alias}) || die "can't open " . $self->{DIRS}->{$dir_alias} . ":$!";
        				grep {$self->{CACHE}->{$dir_alias} .= $_ . " "} readdir (DIR);
        				closedir(DIR);
        			} else {
        				print "ERROR: Can't cache a single file\n";
        				next;
        			}
        		#}
        	}
        }
        --snip--
        
        So you see, I want to be able to load a special function that refrences the %CACHE property of the object. I want to do something like this:
             # assuming MikeCache has been created
             $mikeCache->LoadSpecialBuilder(ALIAS => \&Func);
        
        You see in the source for the cache object above that I want to just run the special builder function using the $self object in the Build() method with no arguements. Yet, I think that there might be some scoping problems--Like how do I get at the %{$self->{CACHE}} property? Do I have to pass in the $self object? Doesn't this kinda defeat the purpose of object programming? Is there a better way to do what I am trying to do here?

        This has been R A D...
      You could pass the speciality "sub" as a string rather than as a code ref. Then you could eval the string in the package context and stick the coderef from that in your structure. Would that work?

      --
      $you = new YOU;
      honk() if $you->love(perl)

        Good question. I'll try it, along with some other ideas that I have. I'll report back and tell everybody what the best solution was...
        Until then :)


        This has been R A D...