This and other RFCs are available on the web at
http://dev.perl.org/rfc/
=head1 TITLE
Objects: Revamp tie to support extensibility (Massive tie changes)
=head1 VERSION
Maintainer: Nathan Wiger <[EMAIL PROTECTED]>
Date: 07 Sep 2000
Mailing List: [EMAIL PROTECTED]
Version: 1
Number: 200
Status: Developing
Requires: RFC 159, RFC 174
=head1 ABSTRACT
C<tie> is really cool. Mostly. It has an amazing amount of power in
concept, but suffers from several limitations which this RFC attempts to
address.
=head1 DESCRIPTION
=head2 Overview
Many people have expressed problems with tie, including Larry [1].
C<tie> suffers from several limitations:
1. It is non-extensible; you are limited to
using functions that have been implemented
with tie hooks in them already.
2. Any additional functions require mixed calls
to tied and OO interfaces, defeating a chief
goal: transparency.
3. It is slow (at least slower than other objects)
4. You can't easily integrate tie and operator
overloading.
5. If defining tied and OO interfaces, you must
define duplicate functions or use typeglobs.
6. Some parts of the syntax are, well, kludgey
This RFC attempts to address all of these points with some changes to
syntax and implementation concepts. It interacts with the concept of
B<polymorphic objects>, described in L<RFC 159>, to provide a simple and
extensible framework.
=head2 New Concepts
This RFC proposes two key principles that will provide a more
general-purpose C<tie> framework:
1. Operator, data, and syntax overloading will be
done via the ALLCAPS methods described in RFC 159.
2. All other functions are directly translated via the
indirect object syntax.
In addition, the declaration of a tie statement is suggested to be
changed into a standard indirect object function:
$object = tie Tie::Class @array_to_tie;
The default C<tie>ing would be performed by C<UNIVERSAL::tie>, which
would be a new method that properly "blessed" the tied variable and then
simply turned around and called the class's C<TIE> method, similar to
how the builtin C<tie> works currently.
There are many changes, so let's go through them one at a time and then
revisit how they will all tie (ha-ha) together at the end.
=head2 Syntax Changes
=head3 Drop C<tie> builtin and replace with C<UNIVERSAL::tie>
As mentioned above, this allows us to call C<tie> in a simple indirect
object form. This eliminates one more special-case function which
currently requires that quotes be placed around the class name. This
syntax should simply be modified to be called on the object it will be
tied to, since C<tie> is after all an object constructor.
=head3 Merge C<TIESCALAR>, C<TIEHASH>, and C<TIEARRAY> into C<TIE>
In practice, people rarely make a class that C<tie>s multiple data types
through the same interface. The reason is that C<STORE>, C<FETCH>,
C<DESTROY>, and other methods overlap. As such, it is more feasible to
create several different modules; witness C<Tie::Array>, C<Tie::Scalar>,
C<Apache::Session>, and other modules.
=head3 Drop C<TIEHANDLE>
Thanks to the below syntax, differentiating between filehandles and
other scalars is no longer important. It would also be very difficult to
make this distinction, since in Perl 6 filehandles are intended to be
C<$scalars>.
=head3 Continue to do data handling through ALLCAPS methods
This will not change. C<STORE> and C<FETCH>, along with other functions
described in RFC 159 and below, will continue to do data handling. In
addition, these methods will be used for operator overloading as well.
=head3 Perform functions through the indirect object syntax
Currently, C<tie> incurs a fairly substantial overhead (depending on
your application) because function calls such as this:
push @tied_array, $value;
Must be internally transformed into this:
$self->PUSH($value);
In addition, you are bound by the methods that the designers have
implemented. While Perl 5.6 finally has a fairly substantial collection,
nonetheless it is easy to imagine that future functions will arise which
you want to C<tie>, but which support has not been added for yet.
Plus, if you want to support extra methods of your own, you must mix
object and tied calls:
$obj = tie %trans, 'Transaction';
$trans{$var} = $value;
$obj->lock($var);
Unfortunately, this defeats one of the key purposes of tie, which is OO
transparency. And, creating a class that supports both OO and tied
interfaces is difficult, requiring typeglobs or duplicate handler
functions.
Instead, this RFC proposes that C<tie>'s operation become much more
fundamental, simply translating functions via the existing indirect
object syntax:
tie Transaction %trans; # indirect object constructor
$trans{$var} = $value ; # $obj->STORE($var, $value);
lock $trans{$var}; # $obj->lock($var);
delete $trans{$var}; # $obj->delete($stuff);
foo %trans; # $obj->foo;
%trans = (); # $obj->CLEAR;
Note that operator and data access is still done by ALLCAPS methods, in
fact the same ones described in L<RFC 159>. The reason for this is
symmetry: Like polymorphic objects, we can now warp our C<tie>d classes
in whatever way we desire. In fact, one could imagine a simple matrix
math class:
tie My::Matrix @a;
@a + @b; # $obj->ADD(@b);
$a[0] = 4; # $obj->STORE(0, 4);
@a * @b; # $obj->MUL(@b);
special_stuff @a; # $obj->special_stuff;
We also no longer have to care about the differences between filehandles
and other scalars:
tie My::Handle $FILE;
print $FILE @stuff; # $obj->print(@stuff);
flush $FILE; # $obj->flush;
close $FILE; # $obj->close
In each of these examples, the variable that is being C<tie>d acts like
little more than a syntactic mask over an object. Translation is
potentially much faster because the indirect object syntax is used. Note
that this can even be accomplished for functions such as push (where a
comma follows the object) if L<RFC 174>, which addresses changes to
indirect object parsing, is adopted.
=head3 C<untie> should take all references out of scope
When called, C<untie> currently suffers the somewhat nasty problem of
not being able to automatically destroy inner references. This means if
you've mixed OO and C<tie>d calls, you may not be able to destroy your
tied object as easily as you like. C<untie> should forceably destroy
hanging references. [2]
=head2 Function Summary
This is a summary of all the functions that should be implemented in
C<tie> in Perl 6. Any functions not mentioned here should be dropped
from the C<tie> interface in Perl 6, instead replaced with the automatic
indirect object calling form:
General Methods
-----------------------------------------------------
TIE Constructor
DESTROY Destructor
STORE Data storage
FETCH Data retrieval
Hash-Specific Methods
-----------------------------------------------------
FIRSTKEY Get first key during keys/values/each
NEXTKEY Iterate through keys/values/each
CLEAR Clearing or resetting of hash
Array-Specific Methods
-----------------------------------------------------
FETCHSIZE scalar @array (basically)
STORESIZE Set $#array
EXTEND Pre-extend array size
CLEAR Clearing or resetting of array
Other Methods
-----------------------------------------------------
Include all other methods described in RFC 159
That's it. Everything else can be automatically invoked through a
slightly-modified indirect object syntax (including C<exists>,
C<delete>, and so forth).
=head2 Example: Transaction
Here is an example of how a tied class may be implemented under this
RFC:
# A class to do some simple transactional locking
# A much more robust version could implement RFC 130
package Transaction;
use Carp;
use strict;
# Include tied interface
sub TIE {
my $self = self; # RFC 152 :-)
bless {@_}, $self;
}
# And also include OO interface per RFC 189
# Note: For both of these, simply allow UNIVERSAL::new and
# UNIVERSAL::tie to take care of the actual calls.
sub NEW {
my $self = self;
bless {@_}, $self;
}
sub RENEW {
croak "Fatal: Reblessing transactional hashes not allowed!";
}
# Include those functions we want to override
# Our internal data functions are in ALLCAPS and most come
# from RFC 159 (as well as previous tie implementations)
sub STORE {
my $self = self;
if ($self->{LOCKED}->{$_[0]}) {
croak "Fatal: Attempt to modify locked key $_[0]!";
}
$self->{DATA}->{$_[0]} = $_[1];
}
sub FETCH {
my $self = self;
return $self->{DATA}->{$_[0]};
}
# Hash-specific method
sub CLEAR {
my $self = self;
# Check for any locked values still remaining
if (keys %{$self->{LOCKED}}) {
croak "Fatal: Attempt to clear hash with locked keys!";
}
undef $self->{DATA};
}
# Want to override what each() and keys() do
# Mostly stolen from Camel-3 p. 383
sub FIRSTKEY {
my $self = self;
my $temp = keys %{$self->{DATA}};
return scalar each %{$self->{DATA}};
}
sub NEXTKEY {
my $self = self;
return scalar each %{$self->{DATA}};
}
# Override addition just for demonstration purposes
sub ADD {
my $self = self;
$self->{DATA}->{$_[0]} += (rand * $_[1]);
}
# Now add any Perl or custom functions that we want these
# objects to be able to handle
sub lock {
my $self = self;
$self->{LOCKED}->{$_[0]} = 1;
}
sub unlock {
my $self = self;
carp "Warning: Key $_[0] already unlocked"
unless $self->{LOCKED}->{$_[0]};
delete $self->{LOCKED}->{$_[0]};
}
sub unlock_all {
my $self = self;
carp "Notice: All values already unlocked" unless
$self->{LOCKED};
undef $self->{LOCKED};
}
# Warn if we have locked values still
sub DESTROY {
my $self = self;
if (keys %{$self->{LOCKED}}) {
carp "Warning: Destroying transaction with locked keys!";
}
undef $self->{LOCKED};
undef $self->{DATA};
}
# Use our Transaction class
package main;
use CGI;
my $cgi = new CGI;
tie Transaction %trans; # Transaction->TIE (thru UNIVERSAL::tie)
# Generate our session id
# Yes I know this is massively insecure ;-)
srand;
$trans{session} = rand;
# All of these call $obj->STORE($var)
$trans{name} = $cgi->param('name');
$trans{email} = $cgi->param('email');
$trans{cc} = $cgi->param('cc');
$trans{amount}= $cgi->param('amount');
# Lock our amount while we're charging the card...
lock $trans{cc}; # $obj->lock('cc');
lock $trans{amount}; # $obj->lock('amount');
for ($try = 0; $try < 3; $try++) {
# Attempt to charge them
next unless &charge_card($trans{cc}, $trans{amount});
$trans{chargedate} = localtime;
}
unlock $trans{cc}; # $obj->unlock('cc');
# Check if we were successful
die "Could no charge card $trans{cc}" unless $trans{chargedate}
# Increment our session id
# ++$trans{session} calls $obj->STORE($obj->ADD('session', 1))
$cgi->param('session') = ++$trans{session};
# Kill our transaction
unlock_all %trans; # $obj->unlock_all;
Note how we are easily able to add three new methods, C<lock>,
C<unlock>, and C<unlock_all>, which are directly translated for us,
meaning we don't have to mix OO and tied variable calls. This provides
true object transparence. Note also how our overloaded C<ADD> operator
is used to increment our session number as well, all transparently to
the user.
=head1 IMPLEMENTATION
Conceptually, implementation is straightforward, but quite different
from tie's current form:
1. Drop C<tie> builtin and replace with C<UNIVERSAL::tie>
2. Merge all constructors into single C<TIE> function
3. Drop internal function translation and instead
translate based on modified indirect object syntax
4. Modify C<untie> to forceably destroy all references
Actually, C<tie> might be able to be handled completely by a
preprocessor of sorts. Consider this code:
tie My::Matrix @a, [ 1, 2, 3 ];
@a = @a + @b; # $obj->CLEAR; $obj->STORE($obj->ADD(@b));
@a = @a * @b; # $obj->CLEAR; $obj->STORE($obj->MUL(@b));
push @a, $value; # $obj->push($value);
If Perl simply ran something like a C<s/\@a\b\s*,?/\$obj/g> over the
code internally, then all of this would become:
$obj = My::Matrix->tie([1, 2, 3]);
$obj = $obj + @b; # $obj->CLEAR; $obj->STORE($obj->ADD(@b));
$obj = $obj * @b; # $obj->CLEAR; $obj->STORE($obj->MUL(@b));
push $obj $value; # $obj->push($value);
And now it is easy to reparse and call functions/operators correctly
simply based on the indirect object syntax and concept of polymorphic
objects.
In any event, this is a massive oversimplification, but at the very
least it sheds some additional light on the concept. C<tie> is used to
hide polymorphic objects, but the actual operator and data overloading
methods are simply member functions of these objects, similar to
currently. The hooks C<tie> currently relies on for functions are no
longer necessary and instead replaced with low-level hooks for
polymorphic objects.
=head1 MIGRATION
Syntactically, many users will only see that some functions have changed
case or otherwise been altered slightly. As such, the p52p6 translator
should be able to change this relatively easily.
Many of the changes in this RFC build on and add power to C<tie>, so do
not require translation because they are new.
=head1 NOTES
[1] http://www.mail-archive.com/perl6-language@perl.org/msg02087.html
[2] Camel-3 p. 395 has an excellent description of this problem.
=head1 REFERENCES
RFC 159: True Polymorphic Objects
RFC 174: Parse C<func($obj, @args)> as C<func $obj (@args)>
RFC 189: Objects : Hierarchical calls to initializers and destructors
RFC 152: Replace $self in @_ with self() builtin (not $ME)
RFC 130: Transaction-enabled variables for Perl6
Camel-3 Chapter on C<tie>, p363-398
Thanks to Nathan Torkington for his input and support