Hi,

As a very green Clojure user and a big fan of persistent data
structures, I'm struggling to grasp the significance of transients in
Clojure 1.1. In particular, the implementation seems to be less safe
and less consistent than it could be.

Consider this somewhat silly REPL session:

user=> (def t (transient []))
#'user/t
user=> (def t2 (conj! t 1))
#'user/t2
user=> (def t3 (conj! t2 2))
#'user/t3
user=> (persistent! t)
[1 2]

This corresponds to an equivalent interaction with a persistent vector
until the last line, when the original vector is made persistent, and
is found to contain the elements that were conj!-ed onto the later
versions! The problem is that each conj! operation mutates the data
structure, but leaves the old reference to it around:

user=> t
#<TransientVector clojure.lang.PersistentVector
$transientvec...@55dec1dd>
user=> t2
#<TransientVector clojure.lang.PersistentVector
$transientvec...@55dec1dd>
user=> t3
#<TransientVector clojure.lang.PersistentVector
$transientvec...@55dec1dd>

(note: all three names refer to the same object)

This means that although a properly "functional" vector-building
sequence will do the right thing (that is, the same thing it would do
with a persistent vector), if your code is not "correct" you get not a
failure, but a different result than you would otherwise. In the above
example, the final result is [1,2] instead of []. I think a non-silly
example could probably be constructed, where this kind of thing
happens for a better reason (say, if you build a vector by visiting
some other data structure, get to the end and for some reason end up
returning the version before you added the last element -- which might
be the easiest way to do it in some cases and works perfectly well if
you're using regular persistent vectors).

My question is: why not have each conj! produce a new, lightweight
TransientVector instance pointing to the same shared data? The
previous instance would be marked as dead, in the same way as the
single instance is currently made a zombie when persistent! is finally
called. The result would be that as soon as the program deviated from
single-path usage of the transient, an exception would immediately be
thrown. This would signal the programmer that either the code has a
bug or it simply isn't suited to use transients.

My guess is that the resulting ephemeral garbage would have only a
small effect on performance, retaining most of the advantage of
transients, but improving their safety. Would any of the Clojure
experts care to comment on whether this seems like a worthwhile
exercise?

I don't think it would be hard for someone more familiar with the
implementation to prototype this, and I'd be very interested in a
report of how well it works and any performance impact. If no one
bites, I'll probably eventually get around to giving it a try, in
which case I'll definitely post my observations.

- moss

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to