Clojure中的var
初学者会对这样的代码感到困惑, #'是什么鬼?
(mount/defstate http-server
:start
(http/start
(-> env
(assoc :handler #'handler/app) ;;<- 此处
(update :port #(or (-> env :options :port) %))))
:stop
(http/stop http-server))
首先, #'是一个读取宏(reader macro), 在reader期间被展开为special form (var...)
(doc var)
一个var可以被deref(deref的读取宏表示是@, 和其他的ref type, 比如atom是一样的), 一个变量(或者是函数), var -> deref 后还会得到其对应的值.
(def x 100)
(identical? (deref (var x))
x)
var可以是指向一个变量
(def a 10000)
(type #'a)
(= (var a) #'a)
var也可以指向一个函数, 指向函数的var可以当成函数来使用, 这个特性对应下文重要应用场景.
(def f +)
(#'f 100 100)
等价于:
(@#'f 100 100)
也等价于:
(f 100 100)
我们需要搞懂var, Var是一个类型clojure.lang.Var, 用于关联symbol和symbol和它的值, 而且可以持有一些列的meta info.
一个symbol, 比如a, 在代码执行期间, 通过def, defn, intern等过程, 关联一个var, 并且这个var可以定义一些meta info, 并关联一个表达式.
在执行期间, symbol会通过它关联的var, 求值得到其具体的值.
需要特别注意的是clojure是一门immutable的语言, 除了三种特殊的引用类型(var, atom, agent)之外, 其他都是不可变的, 所以symbol不可变, symbol通过var关联的value(或者表达式)也不可变.
但是var本身是可变的!
var的改变, 不影响var改变之前的求值结果.
(def a nil)
(def a-list [a]) ;; a 已经被求值为nil
(def a 100)
(first a-list)
使用var的时候, 我们还是会得到var指向的新值100:
(def a nil)
(def a-list [#'a]) ;; list中放置的不是var的值, 而是var本身
(def a 100)
@(first a-list) ;; 此时是var的值
代码执行的时候, 要通过引用去找到相应的值(可能是函数, 也可能是某个变量). 多了一层间接, 保证app函数被修改并且重新编译以后, start函数使用新的版本. 注意前文提到过var类型可以作为它指向的函数来使用.
(defn wrap-inc [f]
(fn [x]
(inc (f x))))
(defn your-handler [x] x)
(def your-app-with-var (wrap-inc #'your-handler))
(def your-app-without-var (wrap-inc your-handler))
(your-app-with-var 1)
(your-app-without-var 1)
注意下面: 使用var以后, 外部函数使用了新定义的your-handler函数
(defn your-handler [x] 10)
(your-app-with-var 1)
(your-app-without-var 1)
var的这个特性在clj/cljs的repl, 也就是开发阶段特别有用.
因为我们总希望刚写的函数在修改并且eval以后立即生效. 希望这个解释能够让我们明白#'的意义/
本质上, var在这里解决的是依赖问题. 开发阶段的状态管理, 更多需要了解mount库.