为何Clojure中使用(last acc)的loop函数执行失败,另一版本正常?
为什么
build-vector抛出空指针异常而build-vector-2正常运行? 先看两个函数的核心代码对比:
; 触发NPE的版本 (defn build-vector [n] (loop [acc [] n 0] ; 此处的n遮蔽了函数参数的n (if (>= (last acc) n) ; 第一次循环时(last acc)为nil acc (recur (conj acc n) (inc n))))) ; 正常运行的版本 (defn build-vector-2 [n] (loop [acc [] m 0] (if (>= m n) ; 直接用循环变量与函数参数n比较 acc (recur (conj acc m) (inc m)))))
关键问题出在第一次循环的判断逻辑:
build-vector的致命错误:- 首先存在变量遮蔽问题:loop绑定的
n完全覆盖了函数传入的参数n,循环里的n从0开始递增,和你调用时传入的9没有任何关系。 - 更直接的崩溃原因:第一次循环时
acc是空向量[],调用(last acc)会返回nil。Clojure的>=运算符只能处理数字类型,当尝试用它比较nil和数字0时,底层的Numbers.gte方法无法识别非数字的nil,直接抛出NullPointerException,这就是异常栈显示的根源。
- 首先存在变量遮蔽问题:loop绑定的
build-vector-2为什么能正常运行?- 它使用了独立的循环变量
m,没有遮蔽函数参数n,判断条件(>= m n)始终是在比较循环变量m(从0开始递增)和传入的目标值9,全程都是数字间的合法比较,不会出现nil。 - 第一次循环时
0 >= 9不成立,执行recur把0加入acc,m递增到1;后续循环直到m等于9时,判断条件成立,返回构建好的向量,完全符合预期。
- 它使用了独立的循环变量
额外提示:
build-vector不仅有NPE问题,变量遮蔽还导致它的逻辑完全偏离了“构建到n的向量”的目标——哪怕修复了NPE,它的判断条件(>= (last acc) n)里的n是循环变量,会陷入无限循环:因为last acc永远是当前n-1,永远小于n,循环永远不会停止。而build-vector-2的逻辑是正确的,最终会返回[0 1 2 ... 8]。
内容的提问来源于stack exchange,提问作者Julio Marins
相关产品推荐
相关产品推荐

