con-Sequential objector
In which I contemplate the meaning of Sequential
. This is an easy one compared to what you just went through.
There is an interface named Sequential
in the pantheon of Clojure collection-related interfaces. It is defined thus:
type Sequential =
interface
end
In other words, a marker interface, so-called. A class implements it to make a statement about itself; in this case, to declare itself sequential.
We should actually have done this for our SimpleCons
example;
type SimpleCons(head:object, tail:object) =
...
interface Sequential
...
Which Clojure collections are Sequential
? Things which have an inherent ordering, such as conses and persistent vectors. Which are not Sequential
? Think of maps and sets; their ordering is essentially random.
There are some odd cases. One might think that a sorted set has an inherent ordering, but apparently its ‘set-ness’ overrides its ‘ordered-ness’.
user=> (sequential? (sorted-set 3 2 1))
false
(The Clojure function sequential?
just checks to see if its argument’s type implements Sequential
.)
Is an ISeq
sequential? One would think so. They can be.
user=> (sequential? (seq (cons 2 ())))
true
user=> (sequential? (seq "abc"))
true
user=> (sequential? (seq {:a 1 :b 2}))
true
It seems reasonble. But there is no guarantee. AFAIK, someone could hand you an ISeq
that is not Sequential
. I’m not sure what that would mean. As you can see above, even the seq
for a map is marked sequential.
Why am I spending time on this simple little interface? Because at first glance, I didn’t understand why it was there. Looking at how Sequential
is used in the Clojure(JVM/CLR) code, every place where Sequential
is checked, when the answer is ‘yes’ there is a call to RT.seq
to get a sequence. Not unreasonable for something that is ‘sequential’. However, if you look at RT.seq
– and as you will see in an upcoming post, I’m spending a lot of time looking at RT.seq
– the call will fail unless the object in question satisfies one of the following:
- It implements
Seqable
. - It is one of small class of special cases: strings, iterables/enumerables, JVM/CLR arrays. (There are special classes providing
ISeq
functionality for these types.)
Why do we need Sequential
?
In the fog at the end of a long day of analysis, I was not finding clarity, so I put this question to the #clojure-dev
channel in the Clojurian slack. And I received a proper education on the matter. (Quite kindly. I botched my initial the statement – it had been a long day – but eventually got something coherent out. And it might have been stated a bit provocatively. And it took me way to long to get the points that others made. Don’t look.)
A good example was from code generating JSON. Maps get encoded differently than ‘sequential’ data structures. The notion of Seqable
things that are not Sequential
is meaningful. Yep, I get that.
My thanks to the patience of the other folks on that thread.
But I still had questions: What is an example of a data structure that is Sequential
but not Seqable
? And how would it fit into Clojure sequence handling?
It turns out there is an example sitting right there in the core.clj
source code: Tne deftype
‘d Eduction
implements Sequential
but not Seqable
. It can survive going through RT.seq
because it implements Iterable
/IEnumerable
, thus meeting one of the RT.seq
’s special cases.
I self-administered one dope-slap for forgetting that there was a second point of extensionality for RT.seq
beyond Seqable
.
I’m happy now. Almost completely. Except for feeling foolish. Except for one last question:
Is there an example of an ISeq
that is not Sequential
? Should that ever happen? (The issue could be forced: Have ISeq
inherit from Sequential
.) Think about a Cons
. It is Sequential
. Its tail defines the rest of its sequence of items. Its tail is an ISeq
. What would it mean to have Cons
with a next
that is not Sequential
?