我很难理解Clojurerest和之间的区别next。官方网站上关于懒惰的页面表明,偏好可能应该是使用rest,但是并不能真正清楚地解释两者之间的区别。谁能提供一些见识?
我很难理解Clojurerest和之间的区别next。官方网站上关于懒惰的页面表明,偏好可能应该是使用rest,但是并不能真正清楚地解释两者之间的区别。谁能提供一些见识?
Answers:
正如您链接的页面所描述的,next它比(的新行为)更严格,rest因为它需要评估惰性cons的结构以知道是返回nil还是seq。
rest另一方面,总是返回一个seq,因此在您实际使用的结果之前,不需要进行任何评估rest。换句话说,rest比懒next。
next总是返回一个cons单元格,而rest仅返回一个ISeq(或它实际上是什么类型)吗?
next返回nil或非空seq。rest始终返回seq,可能为空。
(drop 1 s)令人困惑的是:比起rest它不强制实现s的部分实现,它更为懒惰。rest不能保证只实现延迟序列的第一项。这取决于延迟序列。
这很简单:
(next '(1))
=> nil
所以 next请看下一件事情,如果该行为空,它将返回nil而不是一个空序列。这意味着它需要向前看(返回到它返回的第一项),这使其不能完全懒惰(也许您不需要下一个值,但next浪费了计算时间来向前看)。
(rest '(1))
=> ()
rest 不会向前看,只会返回其余的序列。
也许您认为,为什么还要在这里使用两种不同的东西呢?原因是您通常想知道seq中是否还剩下什么而只是返回nil,但是在某些情况下,性能非常重要,评估另外一项可能意味着您可以付出巨大的努力rest。
next就像(seq (rest ...))。
rest将返回序列的剩余部分。如果该序列的一部分尚未实现,请rest不要强行执行。它甚至不会告诉您序列中是否还有其他元素。
next做同样的事情,但是迫使序列中的至少一个要素得以实现。因此,如果nextreturn nil,您将知道序列中没有其他元素了。
我现在更喜欢next与递归一起使用,因为转义评估更简单/更简洁:
(loop [lst a-list]
(when lst
(recur (next lst))
与
(loop [lst a-list]
(when-not (empty? lst) ;; or (when (seq? lst)
(recur (rest lst))
一种使用情况rest,不过,会如果你使用一个集合作为一个队列或堆栈中。在这种情况下,您希望函数在弹出或出列最后一个项目时返回空集合。
if-let,例如: (loop [lst lst] (if-let [[elem & lst] (seq lst)] (recur lst)))
当编写使用“平凡”递归(使用堆栈)或使用recur(使用尾递归优化,从而实际上循环)遍历序列的代码时,此表非常有用。
注意的行为差异rest和next。与seq此相结合,得出以下成语,即通过测试列表的末尾和通过seq来获得列表的末尾rest(改编自“ Clojure的喜悦”):
; "when (seq s)":
; case s nonempty -> truthy -> go
; case s empty -> nil -> falsy -> skip
; case s nil -> nil -> falsy -> skip
(defn print-seq [s]
(when (seq s)
(assert (and (not (nil? s)) (empty? s)))
(prn (first s)) ; would give nil on empty seq
(recur (rest s)))) ; would give an empty sequence on empty seq
为什么next比这更渴望rest?
如果(next coll)被评估,则结果可以为nil。这必须立即知道(即nil实际上必须返回),因为调用者可以根据的真实性进行分支nil。
如果(rest coll)被评估,则结果不能为nil。除非调用者随后使用函数调用测试结果是否为空,否则可以将lazy-seq中“下一个元素”的生成延迟到实际需要的时间。
例
一个完全懒惰的集合,所有计算都“保持暂停直到需要”
(def x
(lazy-seq
(println "first lazy-seq evaluated")
(cons 1
(lazy-seq
(println "second lazy-seq evaluated")
(cons 2
(lazy-seq
(println "third lazy-seq evaluated")))))))
;=> #'user/x
现在,在第一个“ lazy-seq”处暂停“ x”的计算。
在此之后,我们将使用以下急切方法,进行两次评估:
(def y (next x))
;=> first lazy-seq evaluated
;=> second lazy-seq evaluated
;=> #'user/y
(type y)
;=> clojure.lang.Cons
(first y)
;=> 2
first lazy-seq evaluatednextnil如果右分支为空,则可能必须返回。因此,我们需要更深入地检查一个级别。second lazy-seq evaluatednil,而是返回缺点。first时y,除了从已获得的缺点中检索2之外,没有任何其他操作。使用lazier rest,我们看到一个评估(请注意,必须首先重新定义x才能使这项工作生效)
(def y (rest x))
;=> first lazy-seq evaluated
;=> #'user/y
(type y)
;=> clojure.lang.LazySeq
(first y)
;=> second lazy-seq evaluated
;=> 2
first lazy-seq evaluatedrest永远不会返回nil,即使右侧的lazy-seq求值为空seq。first时y,需要进一步评估lazy-seq,以获取2侧边栏
请注意,y类型为LazySeq。这看起来似乎很明显,但LazySeq不是“语言的事物”,它是“运行时的事物”,不是表示结果而是一种计算状态。实际上(type y),clojure.lang.LazySeq只是意味着“我们还不知道类型,您必须做更多的工作才能找出答案”。每当Clojure函数之类的nil?命中类型clojure.lang.LazySeq就会发生计算!
聚苯乙烯
Clojure中的欢乐,第2版,第126页上,存在使用一个例子iterate来说明之差next和rest。
(doc iterate)
;=> Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free
; of side-effects
事实证明,该示例不起作用。在这种情况下,next和之间的行为实际上没有区别rest。不知道为什么,也许next知道它永远不会回到nil这里,而只是默认为rest。
(defn print-then-inc [x] (do (print "[" x "]") (inc x)))
(def very-lazy (iterate print-then-inc 1))
(def very-lazy (rest(rest(rest(iterate print-then-inc 1)))))
;=> [ 1 ][ 2 ]#'user/very-lazy
(first very-lazy)
;=> [ 3 ]4
(def less-lazy (next(next(next(iterate print-then-inc 1)))))
;=> [ 1 ][ 2 ]#'user/less-lazy
(first less-lazy)
;=> [ 3 ]4
()是正确的,而nil错误的是。因此,(defn keep-going [my-coll] (if (rest my-coll) (keep-going (rest my-coll)) "Finished.")它将永远继续下去-或更确切地说,将使堆栈溢出-同时(defn keep-going [my-coll] (if (next my-coll) (keep-going (next my-coll)) "Finished.")完成。(OP现在肯定已经解决了这个问题;我正在为其他人添加此评论。)