3

我最近才开始学习 Clojure,如果这有点初级,我深表歉意:

有人可以向我解释一下:

=> (def a (lazy-cat
            [0]
            (map inc a)
   ))

=> (take 5 a)
(0 1 2 3 4)

=> (def b (lazy-cat
            [0]
            (map #(inc (nth b %)) (range))
   ))

=> (take 5 b)

IndexOutOfBoundsException   clojure.lang.RT.nthFrom (RT.java:773)

我希望第二个示例以相同的方式运行,使用 b 的第一个元素来计算第二个,然后使用第二个来计算第三个。我的理解是,clojure 甚至不会尝试计算 b 的第三个元素,直到它已经为第二个元素分配了一个值并将其打印在屏幕上。

我很感激有人可以解释这里幕后实际发生的事情。

谢谢 :)

4

2 回答 2

2

这种行为的原因是最简单情况下的map函数实现。(map f colls)看到不同:

user=> (def b (lazy-cat [0] (map (fn [i _] (inc (nth b i))) (range) (range))))
#'user/b
user=> (take 5 b)
(0 1 2 3 4)

这有点令人困惑,但让我解释一下发生了什么。那么,为什么要map改变行为的第二个论点:

https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L2469

(defn map
  ...
  ([f coll]
   (lazy-seq
    (when-let [s (seq coll)]
      (if (chunked-seq? s)
        (let [c (chunk-first s)
              size (int (count c))
              b (chunk-buffer size)]
          (dotimes [i size]
              (chunk-append b (f (.nth c i))))
              (chunk-cons (chunk b) (map f (chunk-rest s))))
        (cons (f (first s)) (map f (rest s)))))))
  ([f c1 c2]
   (lazy-seq
    (let [s1 (seq c1) s2 (seq c2)]
      (when (and s1 s2)
        (cons (f (first s1) (first s2))
              (map f (rest s1) (rest s2)))))))
...

答:优化的原因chunked-seq

user=> (chunked-seq? (seq (range)))
true

因此,值将被“预先计算”:

user=> (def b (lazy-cat [0] (map print (range))))
#'user/b
user=> (take 5 b)
(0123456789101112131415161718192021222324252627282930310 nil nil nil nil)

当然,在你的情况下,这个“预先计算”失败了IndexOutOfBoundsException

于 2013-01-11T18:21:51.350 回答
0

查看源代码take

(defn take
  "Returns a lazy sequence of the first n items in coll, or all items if
  there are fewer than n."
  {:added "1.0"
   :static true}
  [n coll]
  (lazy-seq
   (when (pos? n) 
     (when-let [s (seq coll)]
      (cons (first s) (take (dec n) (rest s)))))))

现在运行第一个案例。没有机会处理越界的数组。

对于您的第二个示例,您正在调用nth一个尚未扩展到 n 个元素的序列。b 将尝试将 0 与依赖于不存在的元素的序列连接起来。

于 2013-01-11T18:16:40.770 回答