带有lazy-seq的Clojure尾递归

时间:2018-03-10 10:56:51

标签: clojure

您能否以更广泛和清晰的方式解释,lazy-seq如何根据此文档页面使尾递归“安全”?

https://clojuredocs.org/clojure.core/lazy-seq

;; The following defines a lazy-seq of all positive numbers.  Note that 
;; the lazy-seq allows us to make a recursive call in a safe way because
;; the call does not happen immediately but instead creates a closure.

user=> (defn positive-numbers 
    ([] (positive-numbers 1))
    ([n] (lazy-seq (cons n (positive-numbers (inc n))))))
#'user/positive-numbers

user=> (take 5 (positive-numbers))
(1 2 3 4 5)

2 个答案:

答案 0 :(得分:1)

如果你看一下lazy-seq的实现,你会发现它确实会返回一个闭包(一个保持上下文的函数):

(list 'new 'clojure.lang.LazySeq (list* '^{:once true} fn* [] body))

因此,positive-numbers中对(cons n (positive-numbers (inc n))的调用不会立即进行评估,而是会延迟直到调用闭包。

答案 1 :(得分:1)

如果你看一下lazy-seq的来源,你会注意到它是一个将其参数打包在函数体中的宏:

user=> (source lazy-seq)
(defmacro lazy-seq
  "Takes a body of expressions that returns an ISeq or nil, and yields
  a Seqable object that will invoke the body only the first time seq
  is called, and will cache the result and return it on all subsequent
  seq calls. See also - realized?"
  {:added "1.0"}
  [& body]
  (list 'new 'clojure.lang.LazySeq (list* '^{:once true} fn* [] body)))

产生类似这样的东西:

user=> (macroexpand '(lazy-seq (cons 1 (lazy-seq [2 3 4]))))
(new clojure.lang.LazySeq (fn* [] (cons 1 (lazy-seq [2 3 4]))))

这会给你一个暗示正在发生的事情:尾部位置的执行被推迟到需要时。这是如何实现的?看看clojure/lang/LazySeq.java (Copyright (c) Rich Hickey. All rights reserved)

final synchronized Object sval(){
  if(fn != null)
    {
                sv = fn.invoke();
                fn = null;
    }
  if(sv != null)
    return sv;
  return s;
}

final synchronized public ISeq seq(){
  sval();
  if(sv != null)
    {
    Object ls = sv;
    sv = null;
    while(ls instanceof LazySeq)
      {
      ls = ((LazySeq)ls).sval();
      }
    s = RT.seq(ls);
    }
  return s;
}

这两个方法执行callable以获取尾值 - 如果他们看到一个,它们也会解除包含的LazySeq。然后缓存结果。