Everything Is a Sequence

Every aggregate data structure in Clojure can be viewed as a sequence. A sequence has three core capabilities:

  • You can get the first item in a sequence:

     (first aseq)

    first returns nil if its argument is empty or nil.

  • You can get everything after the first item—the rest of a sequence:

     (rest aseq)

    rest returns an empty seq (not nil) if there are no more items.

  • You can construct a new sequence by adding an item to the front of an existing sequence. This is called consing:

     (cons elem aseq)

Under the hood, these three capabilities are declared in the Java interface clojure.lang.ISeq. (Keep this in mind when reading about Clojure, because the name ISeq is often used interchangeably with seq.)

The seq function will return a seq on any seq-able collection:

 (seq coll)

seq will return nil if its coll is empty or nil. The next function will return the seq of items after the first:

 (next aseq)

(next aseq) is equivalent to (seq (rest aseq)).

The seq functions work on lists:

 (first '(1 2 3))
 -> 1
 
 (rest '(1 2 3))
 -> (2 3)
 
 (cons 0 '(1 2 3))
 -> (0 1 2 3)

The seq functions work on all other Clojure data structures as well. For example, you can use the seq functions on vectors:

 (first [1 2 3])
 -> 1
 
 (rest [1 2 3])
 -> (2 3)
 
 (cons 0 [1 2 3])
 -> (0 1 2 3)

When you apply rest or cons to a vector, the result is a seq, not a vector. In the REPL, seqs print just like lists, as you can see in the earlier output. You can check the actual returned type by using the seq? predicate:

 (seq? (rest [1 2 3]))
 -> true

The generality of seqs is very powerful, but sometimes you want to produce a specific implementation type. This is covered in Calling Structure-Specific Functions.

You can treat maps as seqs, if you think of a map as a sequence of map entries (where each entry is a key/value pair):

 (first {:fname ​"Aaron"​ :lname ​"Bedra"​})
 -> [:lname ​"Bedra"​]
 
 (rest {:fname ​"Aaron"​ :lname ​"Bedra"​})
 -> ([:fname ​"Aaron"​])
 
 (cons [:mname ​"James"​] {:fname ​"Aaron"​ :lname ​"Bedra"​})
 -> ([:mname ​"James"​] [:lname ​"Bedra"​] [:fname ​"Aaron"​])

You can also treat sets as seqs:

 (first #{:the :quick :brown :fox})
 -> :brown
 
 (rest #{:the :quick :brown :fox})
 -> (:quick :fox :the)
 
 (cons :jumped #{:the :quick :brown :fox})
 -> (:jumped :brown :quick :fox :the)

Maps and sets have a stable traversal order, but that order depends on implementation details, and you shouldn’t rely on it. Elements of a set will not necessarily come back in the order that you put them in:

 #{:the :quick :brown :fox}
 -> #{:fox :the :quick :brown}

If you want a reliable order, you can use this:

 (sorted-set & elements)

sorted-set will sort the values by their natural sort order:

 (sorted-set :the :quick :brown :fox)
 -> #{:brown :fox :quick :the}

Likewise, key/value pairs in maps won’t necessarily come back in the order you put them in:

 {:a 1 :b 2 :c 3}
 -> {:a 1, :c 3, :b 2}

You can create a sorted map with sorted-map:

 (sorted-map & elements)

sorted-maps won’t come back in the order you put them in either, but they will come back sorted by key:

 (sorted-map :c 3 :b 2 :a 1)
 -> {:a 1, :b 2, :c 3}

In addition to the core capabilities of seq, two other capabilities are worth meeting immediately: conj and into.

 (conj coll element & elements)
 (into to-coll from-coll)

conj adds one or more elements to a collection, and into adds all the items in one collection to another. Both conj and into add items at an efficient insertion spot for the underlying data structure. For lists, conj and into add to the front:

 (conj '(1 2 3) :a)
 -> (:a 1 2 3)
 
 (into '(1 2 3) '(:a :b :c))
 -> (:c :b :a 1 2 3)

For vectors, conj and into add elements to the back:

 (conj [1 2 3] :a)
 -> [1 2 3 :a]
 
 (into [1 2 3] [:a :b :c])
 -> [1 2 3 :a :b :c]

Because conj (and related functions) do the efficient thing for the underlying data structure, you can often write code that is both efficient and completely decoupled from a specific underlying implementation.

The Clojure sequence library is particularly suited for large (or even infinite) sequences. Most Clojure sequences are lazy: they generate elements only when they are actually needed. Thus, Clojure’s sequence functions can process sequences too large to fit in memory.

Clojure sequences are immutable: they never change. This makes it easier to reason about programs and means that Clojure sequences are safe for concurrent access. It does, however, create a small problem for human language. English-language descriptions flow much more smoothly when describing mutable things. Consider the following two descriptions for a hypothetical sequence function triple:

  • triple triples each element of a sequence.
  • triple takes a sequence and returns a new sequence with each element of the original sequence tripled.

The latter version is specific and accurate. The former is much easier to read, but it might lead to the mistaken impression that a sequence is actually changing. Don’t be fooled: sequences never change. If you see the phrase “foo changes x,” mentally substitute “foo returns a changed copy of x.”

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.73.127