序列

序列非常類似於列表:它是一個不可變物件,可以在恆定時間內為你提供 first 元素或其元素的 rest 。你還可以通過現有序列來切換新序列,並在開頭貼上一個專案。

你可以使用 seq? 謂詞測試某些內容是否為序列 :

(seq? nil)
;;=> false

(seq? 42)
;;=> false

(seq? :foo)
;;=> false

如你所知,列表是序列:

(seq? ())
;;=> true

(seq? '(:foo :bar))
;;=> true

通過在非空集合上呼叫 seqrseqkeysvals獲得的任何內容也是一個序列:

(seq? (seq ()))
;;=> false

(seq? (seq '(:foo :bar)))
;;=> true

(seq? (seq []))
;;=> false

(seq? (seq [:foo :bar]))
;;=> true

(seq? (rseq []))
;;=> false

(seq? (rseq [:foo :bar]))
;;=> true

(seq? (seq {}))
;;=> false

(seq? (seq {:foo :bar :baz :qux}))
;;=> true

(seq? (keys {}))
;;=> false

(seq? (keys {:foo :bar :baz :qux}))
;;=> true

(seq? (vals {}))
;;=> false

(seq? (vals {:foo :bar :baz :qux}))
;;=> true

(seq? (seq #{}))
;;=> false

(seq? (seq #{:foo :bar}))
;;=> true

請記住,所有列表都是序列,但並非所有序列都是列表。雖然列表在常量時間內支援 peekpopcount ,但通常,序列不需要支援任何這些函式。如果你試圖在一個不支援 Clojure 堆疊介面的序列上呼叫 peekpop,你會得到一個 ClassCastException

(peek (seq [:foo :bar]))
;; java.lang.ClassCastException: clojure.lang.PersistentVector$ChunkedSeq cannot be cast to clojure.lang.IPersistentStack

(pop (seq [:foo :bar]))
;; java.lang.ClassCastException: clojure.lang.PersistentVector$ChunkedSeq cannot be cast to clojure.lang.IPersistentStack

(peek (seq #{:foo :bar}))
;; java.lang.ClassCastException: clojure.lang.APersistentMap$KeySeq cannot be cast to clojure.lang.IPersistentStack

(pop (seq #{:foo :bar}))
;; java.lang.ClassCastException: clojure.lang.APersistentMap$KeySeq cannot be cast to clojure.lang.IPersistentStack

(peek (seq {:foo :bar :baz :qux}))
;; java.lang.ClassCastException: clojure.lang.PersistentArrayMap$Seq cannot be cast to clojure.lang.IPersistentStack

(pop (seq {:foo :bar :baz :qux}))
;; java.lang.ClassCastException: clojure.lang.PersistentArrayMap$Seq cannot be cast to clojure.lang.IPersistentStack

如果你在一個沒有在恆定時間內實現 count 的序列上呼叫 count,你就不會得到錯誤; 相反,Clojure 將遍歷整個序列,直到它到達結尾,然後返回它遍歷的元素數。這意味著,對於一般序列,count 是線性的,而不是恆定的時間。你可以使用 counted? 謂詞測試某些內容是否支援常量時間 count

(counted? '(:foo :bar))
;;=> true

(counted? (seq '(:foo :bar)))
;;=> true

(counted? [:foo :bar])
;;=> true

(counted? (seq [:foo :bar]))
;;=> true

(counted? {:foo :bar :baz :qux})
;;=> true

(counted? (seq {:foo :bar :baz :qux}))
;;=> true

(counted? #{:foo :bar})
;;=> true

(counted? (seq #{:foo :bar}))
;;=> false

如上所述,你可以使用 first 來獲取序列的第一個元素。請注意,first 將在他們的引數上呼叫 seq,因此它可用於任何 seqable,而不僅僅是實際序列:

(first nil)
;;=> nil

(first '(:foo))
;;=> :foo

(first '(:foo :bar))
;;=> :foo

(first [:foo])
;;=> :foo

(first [:foo :bar])
;;=> :foo

(first {:foo :bar})
;;=> [:foo :bar]

(first #{:foo})
;;=> :foo

同樣如上所述,你可以使用 rest 來獲取包含除現有序列的第一個元素之外的所有元素的序列。像 first 一樣,它在論證中稱之為 seq。但是,它並沒有呼叫 seq 它的結果! 這意味著,如果你在一個包含少於兩個專案的序列上呼叫 rest,你將得到 () 而不是 nil

(rest nil)
;;=> ()

(rest '(:foo))
;;=> ()

(rest '(:foo :bar))
;;=> (:bar)

(rest [:foo])
;;=> ()

(rest [:foo :bar])
;;=> (:bar)

(rest {:foo :bar})
;;=> ()

(rest #{:foo})
;;=> ()

如果你想在序列中沒有任何元素時返回 nil,你可以使用 next 而不是 rest

(next nil)
;;=> nil

(next '(:foo))
;;=> nil

(next [:foo])
;;=> nil

你可以使用 cons 函式建立一個新的序列,它將返回 first 的第一個引數和 rest 的第二個引數:

(cons :foo nil)
;;=> (:foo)

(cons :foo (cons :bar nil))
;;=> (:foo :bar)

Clojure 提供了一個大型序列庫,它具有許多處理序列的功能。這個庫的重要之處在於它適用於任何 seqable,而不僅僅是列表。這就是為什麼序列的概念是如此有用的原因; 這意味著像 reduce這樣的單一功能可以完美地適用於任何集合:

(reduce + '(1 2 3))
;;=> 6

(reduce + [1 2 3])
;;=> 6

(reduce + #{1 2 3})
;;=> 6

序列有用的另一個原因是,由於它們不要求 firstrest 的任何特定實現,它們允許惰性序列,其元素僅在必要時實現。

給定一個可以建立序列的表示式,你可以將該表示式包裝在 lazy-seq 巨集中以獲取一個類似於序列的物件,但只有當 seq 函式要求它執行此操作時才會實際評估該表示式,此時它將快取表示式的結果並將 firstrest 呼叫轉發到快取的結果。

對於有限序列,惰性序列通常與等效的急切序列相同:

(seq [:foo :bar])
;;=> (:foo :bar)

(lazy-seq [:foo :bar])
;;=> (:foo :bar)

但是,對於無限序列,差異變得明顯:

(defn eager-fibonacci [a b]
  (cons a (eager-fibonacci b (+' a b))))

(defn lazy-fibonacci [a b]
  (lazy-seq (cons a (lazy-fibonacci b (+' a b)))))

(take 10 (eager-fibonacci 0 1))
;; java.lang.StackOverflowError:

(take 10 (lazy-fibonacci 0 1))
;;=> (0 1 1 2 3 5 8 13 21 34)