You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Clojure中Atom与闭包的行为差异:计数器函数疑问

Clojure闭包与可变状态的差异解析

我在学习Clojure官方文档中的memoize示例时,困惑为何mem变量在多次函数调用中始终持有同一个atom对象。虽相关问答指出这是闭包的特性,但以下两个计数器函数表现截然不同,下面从闭包和可变状态的角度解析二者的核心差异:

两个对比的计数器实现

正常递增的计数器(基于atom)

(def counter
  (let [count (atom 0)]
      (fn [] (do (swap! count inc)
                 @count))))

(println (counter)) ;; 1
(println (counter)) ;; 2

无法递增的计数器(基于普通map)

(def counter2
  (let [count {:value 0}]
      (fn [] (do (assoc count :value (inc (get count :value)))
                  (get count :value))))

(println (counter2)) ;; 0
(println (counter2)) ;; 0

核心差异解析

  • 闭包捕获的是变量引用:两个计数器的闭包都捕获了let块中count变量的引用,但二者指向的对象特性完全不同。
  • 不可变map的特性限制:Clojure的map是不可变数据结构,assoc操作不会修改原map对象,而是返回一个全新的map。counter2中每次调用函数时,原count变量始终指向最初的{:value 0},读取的自然是初始值,所以结果一直为0。
  • atom作为可变容器的作用:atom是Clojure提供的可变容器,swap! count inc操作直接修改atom内部存储的值,而count变量始终指向同一个atom对象。每次调用函数时,读取的是容器内更新后的值,因此能实现递增效果。

修改后的可行版本(基于数组)

阅读相关回答后,我将第二个函数修改为以下形式,此时计数器值能正常递增:

(def counter2
  (let [count (int-array '(0))]
      (fn [] (do (aset count 0 (inc (get count 0)))
                 (get count 0))))) ;; 此行非必需,仅为保持格式一致

(println (counter2)) ;; 1
(println (counter2)) ;; 2

原因说明

数组是Clojure中的可变数据结构,闭包捕获的count变量指向同一个数组对象,aset操作直接修改数组内部的元素值,因此每次调用函数时读取的都是更新后的值,实现了递增效果。

内容的提问来源于stack exchange,提问作者toshi-san

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 06:10:12