This meditation was triggered by Re^3: How to tie a hash to a class.
Why can we do this ...
package My::Hash; use strict; use warnings; use parent 'Tie::Hash'; 1;
... but not this?
package My::Hash; use strict; use warnings; use parent 'Tie::StdHash'; 1;
Why do we have to write this instead?
package My::Hash; use strict; use warnings; use Tie::Hash (); our @ISA='Tie::StdHash'; 1;
Or, even worse, this, following the documentation found in Tie::Hash?
package My::Hash; # NO use strict; or things will break! use warnings; require Tie::Hash; @ISA='Tie::StdHash'; 1;
Well, at some point in time, someone must have thought it was clever to put all three classes Tie::Hash, Tie::StdHash, and Tie::ExtraHash into the same file, Tie/Hash.pm. Looking at https://perl5.git.perl.org/perl.git/history/HEAD:/lib/Tie/Hash.pm, that must have happened before 1996-02-03. So this oddity is embedded deep in Perl's history.
At that time, Tie::Hash was still named TieHash, and Tie::StdHash was still named TieHash::Std. Tie::ExtraHash did not exist. And for reasons that are not clear to me, TieHash::Std inherited from TieHash. TieHash::Std reimplemented each and every method, and so only new() was really inherited from TieHash. That is the most useless method in the entire file, because tie never calls it. The TieHash::TIEHASH() constructor adds some extra work just to check for a new() implementation, and that's still there.
Three days after the first version, the classes were renamed to their current names.
Six years later, Tie::ExtraHash appeared in the same file, and it did NOT inherit from Tie::Hash. At the same time, Tie::StdHash stopped inheriting the nonsense new() constructor from Tie::Hash:
# @ISA = qw(Tie::Hash); # would inherit new() only
In all of the following versions up to the latest from 2013, Tie::ExtraHash never started inherited from Tie::Hash, and the @ISA line in Tie::StdHash was never commented back in.
As shown, Tie::ExtraHash never inherited from Tie::Hash, so it is completely safe to move it into a separate file. Just cut the last 13 lines from Tie/Hash.pm and paste them into a new file Tie/ExtraHash.pm:
package Tie::ExtraHash; sub TIEHASH { my $p = shift; bless [{}, @_], $p } sub STORE { $_[0][0]{$_[1]} = $_[2] } sub FETCH { $_[0][0]{$_[1]} } sub FIRSTKEY { my $a = scalar keys %{$_[0][0]}; each %{$_[0][0]} } sub NEXTKEY { each %{$_[0][0]} } sub EXISTS { exists $_[0][0]->{$_[1]} } sub DELETE { delete $_[0][0]->{$_[1]} } sub CLEAR { %{$_[0][0]} = () } sub SCALAR { scalar %{$_[0][0]} } 1;
For the last 16 years, it seems that nobody complained about Tie::StdHash no longer inheriting the useless new() from Tie::Hash. So I am very sure that we can also move that class safely out of Tie/Hash.pm. From the edited Tie/Hash.pm, cut the last 17 lines and paste them into a new file Tie/StdHash.pm. Add a trailing 1; and remove the @ISA line completely:
# The Tie::StdHash package implements standard perl hash behaviour. # It exists to act as a base class for classes which only wish to # alter some parts of their behaviour. package Tie::StdHash; sub TIEHASH { bless {}, $_[0] } sub STORE { $_[0]->{$_[1]} = $_[2] } sub FETCH { $_[0]->{$_[1]} } sub FIRSTKEY { my $a = scalar keys %{$_[0]}; each %{$_[0]} } sub NEXTKEY { each %{$_[0]} } sub EXISTS { exists $_[0]->{$_[1]} } sub DELETE { delete $_[0]->{$_[1]} } sub CLEAR { %{$_[0]} = () } sub SCALAR { scalar %{$_[0]} } 1;
The most obvious problem first: Append a trailing 1; or else loading Tie::Hash will fail.
Now that we have cleaned up everything, legacy knocks at the door, complaining about modules failing to inherit from Tie::StdHash after loading only Tie::Hash. So, we have to load the two modules, just to avoid legacy trouble. Adding two use lines is easy:
use Tie::StdHash; use Tie::ExtraHash;
Both Tie::StdHash and Tie::ExtraHash lack a version number, so add our $VERSION = '1.06' to both files, and bump $VERSION in Tie::Hash to 1.06, too.
All three classes should use strict; use warnings;.
Tie::StdHash and Tie::ExtraHash have no POD at all. We could work around with something like this:
=head1 NAME Tie::StdHash =head1 SYNOPSIS See L<Tie::Hash> =head1 DESCRIPTION See L<Tie::Hash> =cut
Instead of adding workarounds, we could clean up the POD in Tie::Hash, move the parts documenting Tie::StdHash and Tie::ExtraHash into the respective files, and clean up the examples to 21st century Perl.
Here is a diff, based on the current HEAD:
diff -Naur old/lib/Tie/ExtraHash.pm new/lib/Tie/ExtraHash.pm --- old/lib/Tie/ExtraHash.pm 1970-01-01 01:00:00.000000000 +0100 +++ new/lib/Tie/ExtraHash.pm 2018-09-08 19:59:23.713046870 +0200 @@ -0,0 +1,103 @@ +package Tie::ExtraHash; + +use strict; +use warnings; + +our $VERSION = '1.06'; + +=head1 NAME + +Tie::ExtraHash - base class definition for tied hashes + +=head1 SYNOPSIS + + package NewExtraHash; + + use parent 'Tie::ExtraHash'; + + # All methods provided by default, define + # only those needing overrides + # Accessors access the storage in %{$_[0][0]}; + # TIEHASH should return an array reference with the first element + # being the reference to the actual storage + sub DELETE { + $_[0][1]->('del', $_[0][0], $_[1]); # Call the report writer + delete $_[0][0]->{$_[1]}; # $_[0]->SUPER::DELETE($_[1 +]) + } + + + package main; + + tie %new_extra_hash, 'NewExtraHash', + sub {warn "Doing \U$_[1]\E of $_[2].\n"}; + +=head1 DESCRIPTION + +The B<Tie::ExtraHash> class provide most methods for hashes described + in +L<perltie> (the exceptions are C<UNTIE> and C<DESTROY>). They cause +tied +hashes to behave exactly like standard hashes, and allow for selectiv +e +overwriting of methods. + +For developers wishing to write their own tied hashes, the required m +ethods +are briefly defined in L<Tie::Hash>. See the L<perltie> section for m +ore +detailed descriptive, as well as example code. + +=head1 Inheriting from B<Tie::ExtraHash> + +The accessor methods assume that the actual storage for the data in t +he tied +hash is in the hash referenced by C<(tied(%tiedhash))-E<gt>[0]>. Thu +s +overwritten C<TIEHASH> method should return an array reference with t +he first +element being a hash reference, and the remaining methods should oper +ate on the +hash C<< %{ $_[0]->[0] } >>: + + package ReportHash; + + use strict; + use warnings; + use parent 'Tie::ExtraHash'; + + sub TIEHASH { + my $class = shift; + my $storage = bless [{}, @_], $class; + warn "New ReportHash created, stored in $storage.\n"; + $storage; + } + sub STORE { + warn "Storing data with key $_[1] at $_[0].\n"; + $_[0][0]{$_[1]} = $_[2] + } + + 1; + +The default C<TIEHASH> method stores "extra" arguments to tie() start +ing +from offset 1 in the array referenced by C<tied(%tiedhash)>; this is +the +same storage algorithm as in TIEHASH subroutine above. Hence, a typi +cal +package inheriting from B<Tie::ExtraHash> does not need to overwrite +this +method. + +=head1 C<UNTIE> and C<DESTROY> + +The methods C<UNTIE> and C<DESTROY> are not defined in B<Tie::StdHash +>. +Tied hashes do not require presence of these methods, but if defined, + the +methods will be called in proper time, see L<perltie>. + +If needed, these methods should be defined by the package inheriting +from +B<Tie::StdHash>. + +=head1 MORE INFORMATION + +See L<Tie::Hash> + +=cut + +sub TIEHASH { my $p = shift; bless [{}, @_], $p } +sub STORE { $_[0][0]{$_[1]} = $_[2] } +sub FETCH { $_[0][0]{$_[1]} } +sub FIRSTKEY { my $a = scalar keys %{$_[0][0]}; each %{$_[0][0]} } +sub NEXTKEY { each %{$_[0][0]} } +sub EXISTS { exists $_[0][0]->{$_[1]} } +sub DELETE { delete $_[0][0]->{$_[1]} } +sub CLEAR { %{$_[0][0]} = () } +sub SCALAR { scalar %{$_[0][0]} } + +1; diff -Naur old/lib/Tie/Hash.pm new/lib/Tie/Hash.pm --- old/lib/Tie/Hash.pm 2018-09-08 19:29:56.710938333 +0200 +++ new/lib/Tie/Hash.pm 2018-09-08 20:15:20.396843988 +0200 @@ -1,65 +1,35 @@ package Tie::Hash; -our $VERSION = '1.05'; +our $VERSION = '1.06'; =head1 NAME -Tie::Hash, Tie::StdHash, Tie::ExtraHash - base class definitions for +tied hashes +Tie::Hash - base class definition for tied hashes =head1 SYNOPSIS package NewHash; - require Tie::Hash; - @ISA = qw(Tie::Hash); + use strict; + use warnings; + use parent 'Tie::Hash'; sub DELETE { ... } # Provides needed method sub CLEAR { ... } # Overrides inherited method - - package NewStdHash; - require Tie::Hash; - - @ISA = qw(Tie::StdHash); - - # All methods provided by default, define - # only those needing overrides - # Accessors access the storage in %{$_[0]}; - # TIEHASH should return a reference to the actual storage - sub DELETE { ... } - - package NewExtraHash; - require Tie::Hash; - - @ISA = qw(Tie::ExtraHash); - - # All methods provided by default, define - # only those needing overrides - # Accessors access the storage in %{$_[0][0]}; - # TIEHASH should return an array reference with the first element - # being the reference to the actual storage - sub DELETE { - $_[0][1]->('del', $_[0][0], $_[1]); # Call the report writer - delete $_[0][0]->{$_[1]}; # $_[0]->SUPER::DELETE($_[1 +]) - } - - package main; tie %new_hash, 'NewHash'; - tie %new_std_hash, 'NewStdHash'; - tie %new_extra_hash, 'NewExtraHash', - sub {warn "Doing \U$_[1]\E of $_[2].\n"}; =head1 DESCRIPTION -This module provides some skeletal methods for hash-tying classes. Se +e +This class provides some skeletal methods for hash-tying classes. See L<perltie> for a list of the functions required in order to tie a has +h to a package. The basic B<Tie::Hash> package provides a C<new> method +, as well -as methods C<TIEHASH>, C<EXISTS> and C<CLEAR>. The B<Tie::StdHash> an +d -B<Tie::ExtraHash> packages -provide most methods for hashes described in L<perltie> (the exceptio +ns -are C<UNTIE> and C<DESTROY>). They cause tied hashes to behave exact +ly like standard hashes, +as methods C<TIEHASH>, C<EXISTS> and C<CLEAR>. The L<Tie::StdHash> an +d +L<Tie::ExtraHash> classes provide most methods for hashes described i +n +L<perltie> (the exceptions are C<UNTIE> and C<DESTROY>). They cause +tied +hashes to behave exactly like standard hashes, and allow for selective overwriting of methods. B<Tie::Hash> grandfa +thers the C<new> method: it is used if C<TIEHASH> is not defined in the case a class forgets to include a C<TIEHASH> method. @@ -136,39 +106,10 @@ $_[0]{$_[1]} = $_[2] } - -=head1 Inheriting from B<Tie::ExtraHash> - -The accessor methods assume that the actual storage for the data in t +he tied -hash is in the hash referenced by C<(tied(%tiedhash))-E<gt>[0]>. Thu +s overwritten -C<TIEHASH> method should return an array reference with the first -element being a hash reference, and the remaining methods should oper +ate on the -hash C<< %{ $_[0]->[0] } >>: - - package ReportHash; - our @ISA = 'Tie::ExtraHash'; - - sub TIEHASH { - my $class = shift; - my $storage = bless [{}, @_], $class; - warn "New ReportHash created, stored in $storage.\n"; - $storage; - } - sub STORE { - warn "Storing data with key $_[1] at $_[0].\n"; - $_[0][0]{$_[1]} = $_[2] - } - -The default C<TIEHASH> method stores "extra" arguments to tie() start +ing -from offset 1 in the array referenced by C<tied(%tiedhash)>; this is +the -same storage algorithm as in TIEHASH subroutine above. Hence, a typi +cal -package inheriting from B<Tie::ExtraHash> does not need to overwrite +this -method. - =head1 C<SCALAR>, C<UNTIE> and C<DESTROY> -The methods C<UNTIE> and C<DESTROY> are not defined in B<Tie::Hash>, -B<Tie::StdHash>, or B<Tie::ExtraHash>. Tied hashes do not require +The methods C<UNTIE> and C<DESTROY> are not defined in B<Tie::Hash>. +Tied hashes do not require presence of these methods, but if defined, the methods will be called + in proper time, see L<perltie>. @@ -178,6 +119,54 @@ B<Tie::Hash>, B<Tie::StdHash>, or B<Tie::ExtraHash>. See L<perltie/"S +CALAR"> to find out what happens when C<SCALAR> does not exist. +=head1 Legacy + +Old versions of Tie::Hash up to 1.05 included the classes B<Tie::StdH +ash> and +B<Tie::ExtraHash>, requiring the following workarounds to inherit fro +m +B<Tie::StdHash> resp. B<Tie::ExtraHash>: + + package NewStdHash; + + use strict; + use warnings; + + use Tie::Hash; + our @ISA = qw( Tie::StdHash ); + + package NewExtraHash; + + use strict; + use warnings; + + use Tie::Hash; + our @ISA = qw( Tie::ExtraHash ); + +To provide compatibility with old code, B<Tie::Hash> automatically lo +ads +B<Tie::StdHash> and B<Tie::ExtraHash>. + +New code should load and inherit from B<Tie::Hash>, B<Tie::StdHash> a +nd +B<Tie::ExtraHash> via C<use parent>: + + + package NewHash; + + use strict; + use warnings; + use parent 'Tie::Hash'; + + package NewStdHash; + + use strict; + use warnings; + use parent 'Tie::StdHash'; + + package NewExtraHash; + + use strict; + use warnings; + use parent 'Tie::ExtraHash'; + + =head1 MORE INFORMATION The packages relating to various DBM-related implementations (F<DB_Fi +le>, @@ -189,6 +178,8 @@ use Carp; use warnings::register; +use Tie::StdHash; +use Tie::ExtraHash; sub new { my $pkg = shift; @@ -203,7 +194,7 @@ if ($pkg_new and $pkg ne __PACKAGE__) { my $my_new = __PACKAGE__ -> can ('new'); - if ($pkg_new == $my_new) { + if ($pkg_new == $my_new) { # # Prevent recursion # @@ -238,33 +229,4 @@ } } -# The Tie::StdHash package implements standard perl hash behaviour. -# It exists to act as a base class for classes which only wish to -# alter some parts of their behaviour. - -package Tie::StdHash; -# @ISA = qw(Tie::Hash); # would inherit new() only - -sub TIEHASH { bless {}, $_[0] } -sub STORE { $_[0]->{$_[1]} = $_[2] } -sub FETCH { $_[0]->{$_[1]} } -sub FIRSTKEY { my $a = scalar keys %{$_[0]}; each %{$_[0]} } -sub NEXTKEY { each %{$_[0]} } -sub EXISTS { exists $_[0]->{$_[1]} } -sub DELETE { delete $_[0]->{$_[1]} } -sub CLEAR { %{$_[0]} = () } -sub SCALAR { scalar %{$_[0]} } - -package Tie::ExtraHash; - -sub TIEHASH { my $p = shift; bless [{}, @_], $p } -sub STORE { $_[0][0]{$_[1]} = $_[2] } -sub FETCH { $_[0][0]{$_[1]} } -sub FIRSTKEY { my $a = scalar keys %{$_[0][0]}; each %{$_[0][0]} } -sub NEXTKEY { each %{$_[0][0]} } -sub EXISTS { exists $_[0][0]->{$_[1]} } -sub DELETE { delete $_[0][0]->{$_[1]} } -sub CLEAR { %{$_[0][0]} = () } -sub SCALAR { scalar %{$_[0][0]} } - 1; diff -Naur old/lib/Tie/StdHash.pm new/lib/Tie/StdHash.pm --- old/lib/Tie/StdHash.pm 1970-01-01 01:00:00.000000000 +0100 +++ new/lib/Tie/StdHash.pm 2018-09-08 19:57:06.102753649 +0200 @@ -0,0 +1,91 @@ +package Tie::StdHash; + +use strict; +use warnings; + +our $VERSION = '1.06'; + +=head1 NAME + +Tie::StdHash - base class definition for tied hashes + +=head1 SYNOPSIS + + package NewStdHash; + + use strict; + use warnings; + use parent 'Tie::StdHash'; + + # All methods provided by default, define + # only those needing overrides + # Accessors access the storage in %{$_[0]}; + # TIEHASH should return a reference to the actual storage + sub DELETE { ... } + + package main; + + tie %new_std_hash, 'NewStdHash'; + +=head1 DESCRIPTION + +The B<Tie::StdHash> class provide most methods for hashes described i +n +L<perltie> (the exceptions are C<UNTIE> and C<DESTROY>). It cause tie +d hashes +to behave exactly like standard hashes, and allow for selective overw +riting of +methods. + +For developers wishing to write their own tied hashes, the required m +ethods +are briefly defined in L<Tie::Hash>. See the L<perltie> section for m +ore detailed +descriptive, as well as example code. + +=head1 Inheriting from B<Tie::StdHash> + +The accessor methods assume that the actual storage for the data in t +he tied +hash is in the hash referenced by C<tied(%tiedhash)>. Thus overwritt +en +C<TIEHASH> method should return a hash reference, and the remaining m +ethods +should operate on the hash referenced by the first argument: + + package ReportHash; + + use strict; + use warnings; + use parent 'Tie::StdHash'; + + sub TIEHASH { + my $storage = bless {}, shift; + warn "New ReportHash created, stored in $storage.\n"; + $storage + } + sub STORE { + warn "Storing data with key $_[1] at $_[0].\n"; + $_[0]{$_[1]} = $_[2] + } + + 1; + +=head1 C<UNTIE> and C<DESTROY> + +The methods C<UNTIE> and C<DESTROY> are not defined in B<Tie::StdHash +>. +Tied hashes do not require presence of these methods, but if defined, + the +methods will be called in proper time, see L<perltie>. + +If needed, these methods should be defined by the package inheriting +from +B<Tie::StdHash>. + +=head1 MORE INFORMATION + +See L<Tie::Hash> + +=cut + +sub TIEHASH { bless {}, $_[0] } +sub STORE { $_[0]->{$_[1]} = $_[2] } +sub FETCH { $_[0]->{$_[1]} } +sub FIRSTKEY { my $a = scalar keys %{$_[0]}; each %{$_[0]} } +sub NEXTKEY { each %{$_[0]} } +sub EXISTS { exists $_[0]->{$_[1]} } +sub DELETE { delete $_[0]->{$_[1]} } +sub CLEAR { %{$_[0]} = () } +sub SCALAR { scalar %{$_[0]} } + +1;
Should anyone get paranoid about legal stuff: This diff is free software, use it under the same license as Perl (perlartistic).
Alexander
|
|---|