Andrew Hamm <ahamm@mail.com>:
I don't think so. I've felt for a long time that the tie interface
was not quite carefully-enough thought out. I think that at the
bottom there is a philosophical conflict between
(a) Even a tied array should behave like an array.
and
(b) tie() should allow you to overload array syntax with any
weirdo semantics you want.
Usually (a) was the winner in the past, but there has been a trend
toward (b).
For example, consider
($a, $b, $c) = @tiedarray[4,6,3];
The (b) philosophy says that this should translate to
($a, $b, $c) = $tied_object->FETCHSLICE(4,6,3);
but in this case the (a) philosophy won out, and Perl actually behaves
$a = $tied_object->FETCH(4);
$b = $tied_object->FETCH(6);
$c = $tied_object->FETCH(3);
instead. Thinking of useful and interesting semantics that would be
enabled by (b) and are impossible with (a) is left as an exercise.
Sometimes the (a) philosophy rules out interesting and useful
features. For example, consider Tie::File, which ties an array to a
file, one element per line. One might like
delete $FILE[3];
to excise record 3 from the file entirely, so that for example
0 Maximo Perez
1 The Train
2 Luis Melian Lafineur
3 Olimar
4 Brimstone
5 Clubs
becomes
0 Maximo Perez
1 The Train
2 Luis Melian Lafineur
4 Brimstone
5 Clubs
This is not what "delete $array[3]" does on an ordinary array, but it
seems like a useful extension. However, with Perl's present 'tie'
semantics, it is impossible to get the extension to work consistently,
because of:
delete @FILE[2,3];
which should yield this file:
0 Maximo Perez
1 The Train
4 Brimstone
5 Clubs
The problem is that Perl translates this to
$tied_object->DELETE(2);
$tied_object->DELETE(3);
and a naive implementation of DELETE will instead yield
0 Maximo Perez
1 The Train
3 Olimar
5 Clubs
There is really no way around this. The simple fact is that although
we want
delete @FILE[2,3];
and
delete $FILE[2]; delete $FILE[3];
have different meanings and different effects on the array, there is
no way, from behind the tied array interface, to tell which of them
was requested. The only solution is to change the API. But
philosophy (a) says that there is no need to change the API because
what we are trying to do is to make an array behave in a
non-array-like way.
Here is another example, which is simpler and so may be more
illustrative of the conflict. Consider
$z = pop @array;
On an ordinary array, if the array is empty, this returns undef. One
might decide that that is an invariant property of all arrays, and
should be obeyed by tied arrays as well as by regular arrays. That
would suggest that "pop" should be implemented as
if ($tied_object->FETCHSIZE == 0) {
$z = undef; } else {
$z = $tied_object->POP;
}
This is philosophy (a). Or one could take the point of view that the
return value of "pop" on an empty tied array should be dictated by the
author of the tied array class, so that the implementation would be simply
$z = $tied_object->POP;
This is philosophy (b). The initial implementation took philosophy
(a); in 2002 I put in a patch to make it (b) instead, but I am still
wondering whether I might have made a mistake by doing so.
Here's a third example, close to your heart. What does
$z = $tiedarray[$x]
do when $x is negative? For an ordinary array, it esentially does
if (@tied_array < -$x) {
$z = undef; # Out of bounds subscript
} else {
$z = $tied_array[@tied_array + $x];
}
and one might like to require that the semantics of negative values be
the same for all arrays, even tied arrays. Perl 5's original
implementation did do this. If $x was negative, Perl would actually call
$z = $tied_object->FETCH[$tied_object->FETCHSIZE() + $x];
so that FETCH would never know that the index had originally been
negative. In fact, by default, it still does this.
However, as you discovered, this completely rules out a number of
interesting behaviors. And as I discovered, even for tied arrays that
would like to assign the usual meaning to negative subscripts, it can
be useful for the tied methods to find out whether the subscript was
negative. Consider Tie::File again. Suppose someone says
$z = $FILE[-1];
There is a simple and efficient way to retrieve the last record of the
associated file, even if the file is very large. But Tie::File never
gets a chance to do that. Instead, Perl calls
$tied_object->FETCHSIZE
which forces Tie::File to scan the *entire* file and count all the
records; then Perl calls $tied_object->FETCH() with a positive value.
This annoyance was the that the NEGATIVE_INDICES feature was put
into the tied array interface in the first place. You can view this
as yet another step away from (a) toward (b).
One thing you might want to notice here, however, is that these
problems can be tricky. It is hard to see in advance what methods
will really be required. I don't remember who did the original
implementation of tied arrays. (They were missing from Perl up until
version 5.005, I believe.) But I imagine that whoever it was probably
expected that they were providing a fully useful and complete
interface, and perhaps was even a little diffident about the size of
the API, which was much larger than the orresponding API for tied
hashes. And in some sense, I think they *did* provide a fully useful
and complete API. It just wasn't fully complete enough.
Then a couple of years ago, someone got frustrated and put in the
NEGATIVE_INDICES feature, and---here's the punch line---he apparently
thought that this would be enough to properly support arrays with
arbitrary-range indices. If you look at the tests for this feature in
t/op/tiearray.t, you will see that the author thought he was actually
implementing an array with indices -2 .. 2. He even calls his tied
aray class "NegIndex". The problem you point out---that it doesn't
work, and can't be made to work---never occurred to him. He wanted to
fix the Tie::File behavior; NEGATIVE_INDICES was sufficient for this;
and as a bonus, it seemed that this would also be enough to allow
arrays with subscript ranges -2..2---but he totally missed that it was
*not* enough. What a pinhead! I wonder who it could have been? The
initials in the test file say it is someone named "mjd", but I can't
think of anyone offhand who has those initials.
So I have two points in all of this. First, that it might be hard to
see what is the minimal API for emulating all the weird things people
would want to do with an array. You think you have got it all, but
then someone else comes along and wants to do something you hadn't
considered. Rather than continuing to add the features
piece-by-piece, it might be better for someone good at thinking to sit
down and think really hard, not just baout their own need-of-the-week,
but about the Perl array guts-API and what it supports. And I mean
*really* hard. It is not even immediately clear, for example, what
@tied_array = (1, 2, 3);
should do to be most useful.
And second, that there is another point of view, with a consistent
philosophical position, that says that although it is understandable
that you want to do this stuff, that is too bad, and that is not what
tied arrays are for.
Incidentally, similar problems arise with tied hashes.
values(%tied_hash) does not call $tied_object->VALUES(). Instead, it calls
$tied_object->FETCH($tied_object->FIRSTKEY())
$tied_object->FETCH($tied_object->NEXTKEY())
$tied_object->FETCH($tied_object->NEXTKEY())
$tied_object->FETCH($tied_object->NEXTKEY())
...
which again rules out a number of useful behaviors.
I was then going to address your specific proposals, but I realized
that I was about to start writing about how you might not be doing
what you really wanted, and how this was perhaps not the best way to
achieve your ends, and you said you didn't want that, so I won't.
I'll just say that I don't like your proposal, for several reasons,
and I'm glad that it seems unlikely that you'll actually implement it,
and even less likely that any implementation of yours would be
adopted. But I hope at least you feel that I took your question
seriously.