我很难理解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
做同样的事情,但是迫使序列中的至少一个要素得以实现。因此,如果next
return 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 evaluated
next
nil
如果右分支为空,则可能必须返回。因此,我们需要更深入地检查一个级别。second lazy-seq evaluated
nil
,而是返回缺点。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 evaluated
rest
永远不会返回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现在肯定已经解决了这个问题;我正在为其他人添加此评论。)