是的,Clojure 中的 Var 类似于 C 指针。这没有很好的记录。
假设你创建一个函数fred如下:
(defn fred [x] (+ x 1))
这里实际上有 3 件事。首先,fred 是一个符号。符号 fred(无引号)和关键字 :fred(由前导 : 字符标记)和字符串 "fred"(由两端的双引号标记)之间存在差异。对于 Clojure,它们每个都由 4 个字符组成;即关键字的冒号和字符串的双引号都不包含在它们的长度或组成中:
> (name 'fred)
"fred"
> (name :fred)
"fred"
> (name "fred")
"fred"
唯一的区别是它们的解释方式。字符串旨在表示任何类型的用户数据。关键字旨在以可读的形式表示程序的控制信息(与 1=left、2=right 之类的“幻数”相反,我们只使用关键字 :left 和 :right。
符号意味着指向事物,就像在 Java 或 C 中一样。如果我们说
(let [x 1
y (+ x 1) ]
(println y))
;=> 2
然后x指向值1,y指向值2,我们看到打印的结果。
(def ...) 表单引入了 invisible 第三个元素,即 var。所以如果我们说
(def wilma 3)
我们现在要考虑 3 个对象。 wilma 是一个符号,它指向var,而var 又指向值3。当我们的程序遇到符号wilma 时,求值 以找到var。同样,var 被 求值 以产生值 3。所以它就像 C 中指针的 2 级间接。因为符号 和 strong> var 是“自动评估”的,这会自动且不可见地发生,您不必考虑 var(事实上,大多数人并没有真正意识到甚至存在不可见的中间步骤)。
对于我们上面的函数fred,存在类似的情况,除了var指向匿名函数(fn [x] (+ x 1))而不是值3,就像wilma一样。
我们可以“短路” var 的自动评估,例如:
> (var wilma)
#'clj.core/wilma
或
> #'wilma
#'clj.core/wilma
阅读器宏#'(磅引号)是调用(var ...) 特殊形式的简写方式。请记住,像 var 这样的特殊形式是像 if 或 def 这样的内置编译器,并且不与常规函数相同。 var 特殊形式返回附加到符号 wilma 的 Var 对象。 clojure REPL 使用相同的速记打印 Var 对象,因此两个结果看起来相同。
一旦我们有了Var 对象,自动评估就会被禁用:
> (println (var wilma))
#'clj.core/wilma
如果我们想得到wilma指向的值,我们需要使用var-get:
> (var-get (var wilma))
3
> (var-get #'wilma)
3
同样的事情也适用于弗雷德:
> (var-get #'fred)
#object[clj.core$fred 0x599adf07 "clj.core$fred@599adf07"]
> (var-get (var fred))
#object[clj.core$fred 0x599adf07 "clj.core$fred@599adf07"]
#object[clj.core$fred ...] 是 Clojure 将函数对象表示为字符串的方式。
对于网络服务器,它可以通过var? 函数或其他方式判断提供的值是处理函数还是指向处理函数的var。
如果你输入如下内容:
(jetty/run-jetty handler)
双重自动评估将产生处理函数对象,该对象被传递给run-jetty。相反,如果您键入:
(jetty/run-jetty (var handler))
然后将指向处理函数对象的Var 传递给run-jetty。然后,run-jetty 必须使用if 语句或等效语句来确定它收到了什么,如果它收到的是Var 而不是函数,则调用(var-get ...)。因此,每次通过(var-get ...) 都会返回Var 当前指向的对象。所以,Var 就像 C 中的全局指针或 Java 中的全局“引用”变量。
如果你将一个函数对象传递给run-jetty,它会保存一个指向函数对象的“本地指针”,而外界无法更改本地指针所指的内容。
您可以在此处找到更多详细信息:
更新
正如OlegTheCat 所指出的,Clojure 对指向 Clojure 函数的 Var 对象还有另一个技巧。考虑一个简单的函数:
(defn add-3 [x] (+ x 3))
; `add-3` is a global symbol that points to
; a Var object, that points to
; a function object.
(dotest
(let [add-3-fn add-3 ; a local pointer to the fn object
add-3-var (var add-3)] ; a local pointer to the Var object
(is= 42 (add-3 39)) ; double deref from global symbol to fn object
(is= 42 (add-3-fn 39)) ; single deref from local symbol to fn object
(is= 42 (add-3-var 39))) ; use the Var object as a function
; => SILENT deref to fn object
如果我们将 Var 对象视为函数,Clojure 会将其静默解引用到函数对象中,然后使用提供的参数调用该函数对象。所以我们看到add-3、add-3-fn 和add-3-var 这三个都可以工作。这就是 Jetty 中正在发生的事情。它永远不会意识到你给了它一个 Var 对象而不是一个函数,但是 Clojure 神奇地修补了这种不匹配而不告诉你。
侧边栏:请注意,这仅适用于我们的“码头”实际上是
Clojure 包装器代码 ring.adapter.jetty,而不是实际的 Java
webserver Jetty。如果你试图依靠这个技巧
实际的 Java 函数而不是 Clojure 包装器,它会失败。实际上,您必须使用像 proxy 这样的 Clojure 包装器才能将 Clojure 函数传递给 Java 代码。
如果你将 Var 对象用作函数以外的任何东西,你就没有这样的守护天使来拯救你:
(let [wilma-long wilma ; a local pointer to the long object
wilma-var (var wilma)] ; a local pointer to the Var object
(is (int? wilma-long)) ; it is a Long integer object
(is (var? wilma-var)) ; it is a Var object
(is= 4 (inc wilma)) ; double deref from global symbol to Long object
(is= 4 (inc wilma-long)) ; single deref from local symbol to Long object
(throws? (inc wilma-var)))) ; Var object used as arg => WILL NOT deref to Long object
所以,如果你期待一个函数并且有人给你一个指向函数的 Var 对象,你没问题,因为 Clojure 默默地解决了这个问题。如果您期待函数以外的任何东西,而有人给了您一个指向该东西的 Var 对象,那么您只能靠自己了。
考虑这个辅助函数:
(defn unvar
"When passed a clojure var-object, returns the referenced value (via deref/var-get);
else returns arg unchanged. Idempotent to multiple calls."
[value-or-var]
(if (var? value-or-var)
(deref value-or-var) ; or var-get
value-or-var))
现在你可以安全地使用你得到的东西了:
(is= 42 (+ 39 (unvar wilma))
(+ 39 (unvar wilma-long))
(+ 39 (unvar wilma-var)))
附录
请注意,三个二元性可能会混淆问题:
-
var-get 和 deref 都使用 Clojure Var 做同样的事情
- 阅读器宏
#'xxx被翻译成(var xxx)
- 阅读器宏
@xxx被翻译成(deref xxx)
所以我们(令人困惑!)有很多方法可以做同样的事情:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(def wilma 3)
; `wilma` is a global symbol that points to
; a Var object, that points to
; a java.lang.Long object of value `3`
(dotest
(is= java.lang.Long (type wilma))
(is= 3 (var-get (var wilma)))
(is= 3 (var-get #'wilma))
; `deref` and `var-get` are interchangable
(is= 3 (deref (var wilma)))
(is= 3 (deref #'wilma))
; the reader macro `@xxx` is a shortcut that translates to `(deref xxx)`
(is= 3 @(var wilma))
(is= 3 @#'wilma)) ; Don't do this - it's an abuse of reader macros.