SCALAR=Some value
LIST=Foo,Bar,Baz
HASH=Foo:Bar,Baz:Quux
####
DOMAIN=@example.com
USER=sAMAccountName.DOMAIN
PASS=md5sum(PRE.sAMAccountName.POST,15)
PRE=a874f4u
POST=ea748tyoal
MAIL=join(DOT,givenName,sn).DOMAIN
####
%h = (
DOMAIN => '@example.com',
USER => sub { $r->{sAMAcountName} . $c->{DOMAIN} },
PASS => sub { md5sum($c->{PRE} . $r->{sAMAcountName} . $c->{POST}, 15) },
MAIL => sub { join('.', $r->{givenName}, $r->{sn}).$c->{DOMAIN} },
);
####
$out{$key} = $h{$key}->();
####
%out = %h;
####
package Sub::Hash;
use strict;
use warnings;
use Tie::Hash;
our @ISA = qw(Tie::ExtraHash);
our $VERSION = '0.01';
sub TIEHASH {
my $class = shift;
my $sub = shift;
if(ref $sub) {
$sub =~ /\bCODE\(/
or die "$class\::TIEHASH: not a code reference: '$_[0]', aborted";
} else {
my $pack = caller;
my $s = $pack->can($sub)
or die "Can't find subroutine \&$pack\::$sub, aborted";
$sub = $s;
}
my $storage = bless [{}, {}, $sub, map{\$_} @_], $class;
$storage;
}
sub FETCH {
my $self = shift;
my $shash = $self->[0];
my $vhash = $self->[1];
my $key = shift;
my $value = $vhash->{$key};
ref( $shash->{$key} ) =~ /\bCODE\b/
? $shash->{$key}->($key, $value, $self, map{$$_}@{$self}[3..$#$self])
: $value;
}
sub STORE {
my $self = shift;
my $key = $_[0];
my $shash = $self->[0];
my $vhash = $self->[1];
my $gensub = $self->[2];
$vhash->{$key} = $_[1];
unless ($shash->{$key}) {
my $sub = $gensub->(@_,$self, map{$$_}@{$self}[3..$#$self]);
$shash->{$key} = $sub if ref $sub eq 'CODE';
}
}
# accessors for the instance members
sub subhash { $_[0][0] };
sub valhash { $_[0][1] };
sub sub { $_[0][0]->{$_[1]} }
sub value {
if(exists $_[2]) {
$_[0][1]->{$_[1]} = $_[2];
}
$_[0][1]->{$_[1]};
}
sub gensub {
my $self = shift;
if (@_) {
my $sub = shift;
if(ref $sub) {
$sub =~ /\bCODE\b/ or die "not a CODE reference, aborted";
} else {
my $pack = caller;
my $s = $pack->can($sub)
or die "Can't find subroutine \&$pack\::$sub, aborted";
}
$self->[2] = $sub;
}
$self->[2];
}
sub params {
my $self = shift;
if (@_) {
splice @$self, 3, $#$self, map {\$_} @_;
}
map {$$_} @$self[3..$#$self];
}
1;
__END__
=head1 NAME
Sub::Hash - tie generated subroutines to keys of a hash
=head1 SYNOPSIS
use Sub::Hash;
tie %hash, Sub::Hash, qw(gensub), $additonal, $variable;
sub gensub {
my ($key,$value, $additional, $variable) = @_;
...
return $code;
}
# also
$gensub = sub {
my ($key,$value, $additional, $variable) = @_;
...
return $code;
};
tie %hash, Sub::Hash, $gensub, $additonal, $variable;
=head1 DESCRIPTION
This package implements a hash table whose values are anonymous subroutines.
These subroutines are generated by the subroutine passed to tie() as the third
argument when a value is allocated for a key. This subroutine generating
subroutine gets the key/value pair as the first arguments, followed the object
which is returned by tied(%hash) on the tied %hash, then by any additional
variables passed to tie() after the subroutine argument.
Accessing a value results in execution of its associated subroutine, which gets
the same arguments as the the generating subroutine. If the subroutine slot is
empty, the content of the value slot is returned instead.
While the scalar values in a hash slot may be changed like in any other hash,
the associated subroutine remains in place after generation (but see below).
Additional methods to access the components of the object returned by the call
to tie() or tied(%hash) are described below.
=head1 CONSTRUCTOR
The generating subroutine may be passed as an anonymous sub (or CODE reference)
or as a literal to tie(), in which case the subroutine is looked up in the
caller's package.
Additional variables are stored as references to the arguments of the
constructor, which means that their reference count increases, and remain in
place even after their lexical scope ends.
=head1 METHODS
=over 4
=item subhash
returns the anonumous hash holding the value subroutines
=item valhash
returns the anonymous hash holding the scalar values
=item sub($key [,$sub])
gets or sets the anonymous subroutine stored under $key
=item value($key [,$scalar])
gets or sets the scalar value stored in the hash under $key
=item gensub
Gets or sets the subroutine generating subroutine.
=item params
gets or sets the additional parameters passed to the tie() call.
Setting them replaces the original parameters, so their reference
count is decreased.
=back
=head1 EXAMPLE
use Sub::Hash;
my ($closed,$over) = qw(closed over);
my $gensub = sub {
my ($key,$value,$w,$x) = @_;
return sub {
my($key,$value,$y,$z) = @_;
return "closed over:\n"
. "- tied vars at creation: ('$w','$x')\n"
. "- key = $key\n"
. "modifyable:\n"
. "- value = $value\n"
. "- tied variables: ($y,$z)\n";
}
};
my $obj = tie %hash, Sub::Hash, $gensub, $closed, $over;
my $key = 'foo';
my $result = $hash{$key} = 'bar'; # generates the subroutine and executes it
print $result,"-----\n";
($closed,$over) = ('open','til late');
$result = $hash{$key}; # executes sub stored under $key,
# passing it ($key,$value,$obj,$closed,$over)
print $result,"-----\n";
$result = $hash{$key} = 'quux';
print $result,"-----\n";
__END__
Results of the above code:
closed over:
- tied vars at creation: ('closed','over')
- key = foo
modifyable:
- value = bar
- tied variables: (closed,over)
-----
closed over:
- tied vars at creation: ('closed','over')
- key = foo
modifyable:
- value = bar
- tied variables: (open,til late)
-----
closed over:
- tied vars at creation: ('closed','over')
- key = foo
modifyable:
- value = quux
- tied variables: (open,til late)
-----
=head1 SEE ALSO
Tie::Hash, perltie, Tie::Sub
=head1 AUTHOR
Georg Moritz, Eshmem@cpan.orgE
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2015 by Georg Moritz
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=cut