Short answer: absolutely.
Long answer: emphatically not. I believe you have a bit of an XY
problem here. You're wanting transactional security and timely
destruction but have predisposed yourself not to accept them unless
they happen to look like deterministic reference counting. Perl 6
offers the former without offering you the latter, because Perl 6 must
run on a variety of platforms that may or may not support deterministic
reference counting semantics easily. Perl 6 is a language, not an
implementation. As such, it officially Doesn't Care what Parrot does
about GC under the hood.
But the problem goes much deeper than that, and has to do with
the necessary virtualization of time as we scale things up and
distribute responsibility for them. In this day of multiple,
distributed processing units of uncertain proximity, it's going
to get harder and harder to say what a deterministic reference is.
What if a bit your program is running on a different processor and it
goes down? You can't know that till the next GC run at the appropriate
granularity fails to sync with the down processor. A GC solution can
finesse around such problems, up to a point, whereas a deterministic
solution really has only one approach. In the larger and wider scale
of things, you can't know whether a reference is valid till you try
it--see the Web for an example. The web would never have take off with
perfect accountability. (That's why Xanadu didn't work, basically.)
All of computing is getting more like that.
For instance, people are now finding that deterministic locking
solutions don't scale up either. STM (software transactional memory)
seems to be a way to get around that, and that's partly because a
transactional memory manager can virtualize time much like a garbage
collector can defer decisions it isn't sure of. Two transactions
can be in conflict in real time, which would prevent at least one
of them from happening under deterministic locking. But an STM
manager can decide the fate of the transactions based *only* on the
data dependencies, ignoring the bogus realtime dependency, and maybe
decide to allow both transactions if a consistent state is maintained.
In short, programs written in languages that require unnecessary
determinism will not escape the von Neumann bottleneck as easily
as programs written in languages that do not. Determinism is fine
when you need it, and a good language will provide adequate ways to
express that when you do need it. (Perl 6 provides several ways that
are much handier than try/finally, and just about as handy as RAII.)
But baking such handicaps into every object merely guarantees it
will not scale well in the real world. The real world itself avoids
computing deterministically most of the time! And if anything could
manage determinism, it'd probably be the real world! Languages that
require determinism will scale only up only when the entire world is
running one huge completely entangled quantum computer. Determinism
is not interoperable.
Anyway, that's the trend I see. And that's why Perl 6 has lots of
ways to promise you Don't Care about various dependencies, but Do Care
about others. That's why Perl 6 has pipes and lazy lists, junctions
and hyperoperators, contend and defer. These are for building scalable
solutions to run on cell processors and GPUs and clusters of servers
and corporate intranets. And maybe even trigger a Singularity or two. :-)
Larry
Best regards, Blair
Well, there is one general method I can think of. You need to know
more about your program in order to implement it; i.e. if an object
which contains an array of objects, one element of which has a socket
that needs closing, it's going to be tough to get it right. That's
one of the negative sides of the trade-off Larry mentioned (but he
made an excellent case for the positive sides, and the reason why
we're leaning the nondeterministic way).
The main tool at your disposal is the LEAVE closure trait. i.e.:
sub foo {
do_stuff();
do_more_stuff();
LEAVE { say "leaving foo" }
}
This code will say "leaving foo" whenever its dynamic scope ends,
whether that time is the successful completion of do_more_stuff or
whether do_stuff threw an exception. If I understand the idiom
correctly, this exceptional case was the main reason behind RAII. Do
your cleanup in the LEAVE block.
If you have a specific scenario that comes up a lot, other than "the
exact semantics of RAII", do describe it and that will give us
something to think about. Either there's already some clever trick
you can do to handle it, there's a little extra language feature we'll
have to add, or you'll have to put up with doing a little more manual
bookkeeping.
Luke
> If you have a specific scenario that comes up a lot, other than "the
> exact semantics of RAII", do describe it and that will give us
> something to think about. Either there's already some clever trick
> you can do to handle it, there's a little extra language feature we'll
> have to add, or you'll have to put up with doing a little more manual
> bookkeeping.
>
> Luke
I think the most-specific scenario that comes up is: "I have an object
that represents some outside-my-program entity and resource
(file/socket handles, DBUS/DCOM/CORBA things/instances are the prime
examples in my mind) that, once I use and don't need them anymore, I
want them to be freed automagically, to avoid exhausting such
resources. How do I make a class of objects that, like file handles in
Perl5, calls close() when you don't refer to them anymore?"
use strict;
use warnings;
use threads;
use threads::shared;
use Thread::Semaphore;
{
package MyObj;
sub new {
my ($this, $name, $lives) = @_;
my $self = &threads::shared::share({});
$self->{name} = $name;
$self->{lives} = $lives;
$self->{tid} = threads->tid();
print "Creating '$name' with $lives lives\n";
bless $self;
}
sub DESTROY {
my $self = shift;
if (threads->tid() == $self->{tid}) {
print "Destroying '$self->{name}' with $self->{lives} lives\n";
}
}
}
my $s = new Thread::Semaphore;
my @B :shared;
push @B, MyObj->new('zippy', 3);
push @B, MyObj->new('biggles', 5);
my $t1 = threads->new(\&worker, 'T1');
my $t2 = threads->new(\&worker, 'T2');
my $t3 = threads->new(\&worker, 'T3');
$t1->join();
$t2->join();
$t3->join();
exit;
sub worker {
my $name = shift;
print "$name: starting\n";
while (1) {
$s->down;
if (!@B) {
print "$name: Exiting\n";
$s->up;
return;
}
my $o = pop @B;
print "$name: '$o->{name}' has $o->{lives} lives\n";
if ($o->{lives} != 0) {
print "$name: Remove from list\n";
$o->{lives}--;
push @B, $o;
}
$s->up;
sleep 1;
}
}
__DATA__
Blair
Creating 'zippy' with 3 lives
Destroying 'zippy' with 3 lives
Creating 'biggles' with 5 lives
Destroying 'biggles' with 5 lives
T1: starting
T1: 'biggles' has 5 lives
T1: Remove from list
T2: starting
T2: 'biggles' has 4 lives
T2: Remove from list
T3: starting
T3: 'biggles' has 3 lives
T3: Remove from list
T1: 'biggles' has 2 lives
T1: Remove from list
T2: 'biggles' has 1 lives
T2: Remove from list
T3: 'biggles' has 0 lives
T1: 'zippy' has 3 lives
T1: Remove from list
T2: 'zippy' has 2 lives
T2: Remove from list
T3: 'zippy' has 1 lives
T3: Remove from list
T1: 'zippy' has 0 lives
T2: Exiting
T3: Exiting
T1: Exiting
This shows the objects being DESTROYed before they should be, i.e. the
last instance being destroyed in the relevant thread. Maybe P6 can cure
this?
my $a = MyObj->new('zippy', 3);
my $b = MyObj->new('biggles', 5);
my $s = new Thread::Semaphore;
my @B :shared;
push @B, $a;
push @B, $b;
...
Then the destruction does happen at the end of the program. I.e.
Creating 'zippy' with 3 lives
Creating 'biggles' with 5 lives
T1: starting
T1: 'biggles' has 5 lives
T1: Remove from list
T2: starting
T2: 'biggles' has 4 lives
T2: Remove from list
T3: starting
T3: 'biggles' has 3 lives
T3: Remove from list
T1: 'biggles' has 2 lives
T1: Remove from list
T2: 'biggles' has 1 lives
T2: Remove from list
T3: 'biggles' has 0 lives
T1: 'zippy' has 3 lives
T1: Remove from list
T2: 'zippy' has 2 lives
T2: Remove from list
T3: 'zippy' has 1 lives
T3: Remove from list
T1: 'zippy' has 0 lives
T2: Exiting
T3: Exiting
T1: Exiting
All threads joined
Destroying 'biggles' with 0 lives
Destroying 'zippy' with 0 lives
[from the mad hatter: please ignore previous message about object being
destroyed in wrong place]