transients problem

78 views
Skip to first unread message

Alan Forrester

unread,
Feb 24, 2018, 10:15:02 AM2/24/18
to clo...@googlegroups.com
Calling (first #{1}) gives the result 1.

Calling (first (transient #{1})) gives an error:

“IllegalArgumentException Don't know how to create ISeq from: clojure.lang.PersistentHashSet$TransientHashSet clojure.lang.RT.seqFrom (RT.java:550)”

Is this behaviour intended?

And whether or not this is intentional is there a way round it?

Alan Forrester


Timothy Baldridge

unread,
Feb 24, 2018, 10:19:46 AM2/24/18
to clo...@googlegroups.com
Yes, transients mutate when updated. Seqs over sets are a immutable view over a collection. So if someone did get this to work, the implementation could be incorrect. And you could get something really strange like this:

(def s (transient #{1 2}))
(def sq (seq s))
(first sq) => 1
(disj s 1)
(second sq) => nil

There's a dozen things that could go really wrong here, but that's the gist of it. 

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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+unsubscribe@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
“One of the main causes of the fall of the Roman Empire was that–lacking zero–they had no way to indicate successful termination of their C programs.”
(Robert Firth)

Didier

unread,
Feb 24, 2018, 3:33:17 PM2/24/18
to Clojure
Clojure tends to blur the lines between its different types of collections and sequences, because of how most functions often know how to work with all of them and others will coerce them automatically from one type to another. This makes it that 99% of the time, everything just works like magic, but 1% of the time, you can be surprised and its important to understand that and learn how to recognize and debug those situations. The error message: "Don't know how to create X from Y" almost always indicates that you are in such a situation.

In your case, you have to realize that the function `first` works over all types that implement `ISeq` (https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/ISeq.java). But if you look at its documentation: "Returns the first item in the collection. Calls seq on its argument. If coll is nil, returns nil." You can see that it will coerce the input to a ISeq automatically by calling the coercing function `seq` on it. Now `seq` can coerce a lot of things to an ISeq, but not Transient collections. That's why it throws the error "Don't know how to create ISeq from Transient".

The literal #{} constructs a new instance of PersistentHashSet. This type of data-structure does not implement ISeq, but can be coerced to an ISeq by `seq`. When you call `transient` on a PersistentHashSet though, you are coercing it to a TransientHashSet, and that type can not be coerced to a ISeq using `seq`.

So while Clojure doesn't have static types, it still has types, and its important to start recognizing and thinking in terms of them if you want to master Clojure.

I like to use the functions: `type`, `supers`, `bases` to help inspect what type I'm dealing with, and what interface they support. Then I resort to `doc` and `source` to see if the documentation mentions any form of implicit coercion, or if the source shows me which interface it'll actually use. Finally, some core functions delegate to Java for their implementation, so I sometimes go on github and look at the Java source to see what types they work with, such as : https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/RT.java#L524

Hope this helps.
Didier

Didier

unread,
Feb 24, 2018, 4:32:08 PM2/24/18
to Clojure
is there a way round it? 

Something else I need to point out is that you really should not use a Set for order. Sets are specifically unordered, and provide no guarantees of order. Try calling first, second and last on #{1 2 3} for example, you'll probably get things back in a different order.

If it happens to work, it is accidental, and should not be relied on. If you want order, use a Vector or a List. Vectors are probably going to feel more natural, since they append to the end.

You use a Set over a Vector most of the time, because you are specifically telling the consumers of the collection not to rely on order, that the order is meaningless and no logic should be derived from it. Similarly, if you use a Vector over a Set it should be because the order is guaranteed and does matter, and consumers of the collection are welcomed to assume this and build logic that is order dependent.

That said, yes there is a way around it, you can call persistent! to get back a PersistentHashSet, and then call first on it. Going from a transient to a persistent is O(1), so its really fast. You can keep alternating between persistent and transient as your needs change between reading and iterating back to updating.

Reading and iterating over a persistent is just as fast, the transient provides no value. Transient will only make updates and inserts faster.

On Saturday, 24 February 2018 07:15:02 UTC-8, Alan Forrester wrote:

Christophe Grand

unread,
Feb 24, 2018, 6:33:48 PM2/24/18
to clojure
This makes me think that transients could (should?) be made reducible.

Timothy Baldridge

unread,
Feb 24, 2018, 6:44:00 PM2/24/18
to clo...@googlegroups.com
>>  If it happens to work, it is accidental, and should not be relied on. 

Yes, and no. The actual order may change between Clojure releases (last time it changed was with 1.6), or between two equal collections, but the ordering will not change between two calls to `seq` on the same instance of a hashmap or set. Therefore you absolutely can rely on this working: (zipmap (keys m) (vals m)), you will never get a jumbling of keys and values. Most of the time when people call seq on a set or map they want to iterate over it, and that's fine. 

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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+unsubscribe@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Didier

unread,
Feb 24, 2018, 9:13:08 PM2/24/18
to Clojure
Most of the time when people call seq on a set or map they want to iterate over it, and that's fine.

Right, I guess I meant insertion order.

Though, is it not true that conceptually, two implementations of IPersistentSet would not need to return elements in the same order? Even to the point, that a copy of the same concrete implementation isn't even required to do so? I understand this works given Clojure's concrete implementation where the Hash are sorted because they're stored in a Trie, which is ordered, but that's an implementation detail no? And is not mandated by the contract of the interface? Where as, an IPersistentVector must conceptually return elements in insertion order?

On Saturday, 24 February 2018 15:44:00 UTC-8, tbc++ wrote:
>>  If it happens to work, it is accidental, and should not be relied on. 

Yes, and no. The actual order may change between Clojure releases (last time it changed was with 1.6), or between two equal collections, but the ordering will not change between two calls to `seq` on the same instance of a hashmap or set. Therefore you absolutely can rely on this working: (zipmap (keys m) (vals m)), you will never get a jumbling of keys and values. Most of the time when people call seq on a set or map they want to iterate over it, and that's fine. 
On Sat, Feb 24, 2018 at 2:32 PM, Didier <did...@gmail.com> wrote:
is there a way round it? 

Something else I need to point out is that you really should not use a Set for order. Sets are specifically unordered, and provide no guarantees of order. Try calling first, second and last on #{1 2 3} for example, you'll probably get things back in a different order.

If it happens to work, it is accidental, and should not be relied on. If you want order, use a Vector or a List. Vectors are probably going to feel more natural, since they append to the end.

You use a Set over a Vector most of the time, because you are specifically telling the consumers of the collection not to rely on order, that the order is meaningless and no logic should be derived from it. Similarly, if you use a Vector over a Set it should be because the order is guaranteed and does matter, and consumers of the collection are welcomed to assume this and build logic that is order dependent.

That said, yes there is a way around it, you can call persistent! to get back a PersistentHashSet, and then call first on it. Going from a transient to a persistent is O(1), so its really fast. You can keep alternating between persistent and transient as your needs change between reading and iterating back to updating.

Reading and iterating over a persistent is just as fast, the transient provides no value. Transient will only make updates and inserts faster.

On Saturday, 24 February 2018 07:15:02 UTC-8, Alan Forrester wrote:
Calling (first #{1}) gives the result 1.

Calling (first (transient #{1})) gives an error:

“IllegalArgumentException Don't know how to create ISeq from: clojure.lang.PersistentHashSet$TransientHashSet  clojure.lang.RT.seqFrom (RT.java:550)”

Is this behaviour intended?

And whether or not this is intentional is there a way round it?

Alan Forrester


--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.
Reply all
Reply to author
Forward
0 new messages