序列

序列非常类似于列表:它是一个不可变对象,可以在恒定时间内为你提供 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)