何时使用 Var 而不是函数?
When to use a Var instead of a function?
我正在阅读这本书 Web Development with Clojure 它告诉我将处理程序(定义如下)作为 Var 对象而不是函数本身传递,因为函数可以动态更改(这就是wrap-reload 确实如此)。
书上说:
"请注意,为了这个中间件,我们必须从处理程序创建一个 var
上班。这是确保包含当前的 Var 对象所必需的
返回处理函数。如果我们改用处理程序,那么应用程序会
只能看到函数的原始值,更改不会反映出来。”
我不是很明白这是什么意思,vars类似于c指针吗?
(ns ring-app.core
(:require [ring.adapter.jetty :as jetty]
[ring.util.response :as response]
[ring.middleware.reload :refer [wrap-reload]]))
(defn handler [request]
(response/response
(str "<html>/<body> your IP is: " (:remote-addr request)
"</body></html>")))
(defn wrap-nocache [handler]
(fn [request]
(-> request
handler
(assoc-in [:headers "Pragma"] "no-cache"))))
这是处理程序调用:
(defn -main []
(jetty/run-jetty
(wrap-reload (wrap-nocache (var handler)))
{:port 3001
:join? false}))
是的,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 ...)
形式引入了 不可见的 第三个元素,即 Var
。所以如果我们说
(def wilma 3)
我们现在有 3 个对象需要考虑。 wilma
是一个符号,它指向一个 Var
,它又指向值 3
。当我们的程序遇到符号wilma
时,就是求值找到Var
。同样,Var 被 求值 以产生值 3。所以它就像 C 中指针的 2 级间接寻址。因为这两个符号和 Var 是“自动求值”的,这是自动且无形地发生的,您不必考虑 Var(事实上,大多数人并没有真正意识到无形的中间步骤甚至存在)。
对于我们上面的函数 fred
,存在类似的情况,除了 Var 指向匿名函数 (fn [x] (+ x 1))
而不是像 wilma
那样的值 3
。
我们可以像这样“短路”Var 的自动求值:
> (var wilma)
#'clj.core/wilma
或
> #'wilma
#'clj.core/wilma
其中 reader 宏 #'
(引号)是调用 (var ...)
特殊形式的 shorthand 方式。请记住,像 var
这样的特殊形式是像 if
或 def
这样的内置编译器,并且是 而不是 与常规函数相同。 var
特殊形式 return 是附加到符号 wilma
的 Var 对象。 clojure REPL 使用相同的 shorthand 打印 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 将函数对象表示为字符串的方式。
对于 Web 服务器,它可以通过 var?
函数或其他方式判断提供的值是处理函数还是指向处理函数的 var。
如果您键入如下内容:
(jetty/run-jetty handler)
双重自动求值将产生传递给run-jetty
的处理函数对象。相反,如果您键入:
(jetty/run-jetty (var handler))
然后指向处理函数对象的Var
将被传递给run-jetty
。然后,run-jetty
将不得不使用 if
语句或等效语句来确定它收到了什么,如果收到 Var
而不是函数,则调用 (var-get ...)
。因此,每次通过 (var-get ...)
都会 return 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 将 SILENTLY 将其解引用到函数对象中,然后调用该函数对象与提供的参数。所以我们看到 add-3
、add-3-fn
和 add-3-var
这三个都可以工作。这就是 Jetty 中正在发生的事情。它永远不会意识到您给了它一个 Var 对象而不是一个函数,但是 Clojure 在不告诉您的情况下神奇地修补了这种不匹配。
Sidebar: Please note this only works since our "jetty" is actually
the Clojure wrapper code ring.adapter.jetty
, and not the actual Java
webserver Jetty. If you tried to depend on this trick with an
actual Java function instead of a Clojure wrapper, it would fail. Indeed, you must use a Clojure wrapper like proxy
in order to pass a Clojure function to Java code.
如果您将 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
- reader宏
#'xxx
翻译成(var xxx)
- reader宏
@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.
另注
(def ...)
特殊形式 return 是它创建的 clojure.lang.Var
对象。通常,(def ...)
形式仅在 Clojure 源文件(或 REPL)的顶层使用,因此 return 值被静默丢弃。但是,也可以捕获对创建的 Var
对象的引用:
(let [p (def five 5)
q (var five)]
(is= clojure.lang.Var
(type p)
(type q))
(is= 6
(inc five)
(inc (var-get p))
(inc (deref q)))
(is (identical? p q)))
这里我们创建了一个指向数字5
的全局变量five
。 def
形式的 return 值在本地值 p
中被捕获。我们使用 (var ...)
特殊形式来获得指向同一个 Var
对象的引用 q
。
第一个测试表明p
和q
都是clojure.lang.Var
类型。中间测试显示了三种访问值 5
的方法。正如预期的那样,所有检索值 5
递增以产生 6
。最后一个测试验证 p
和 q
都指向同一个 Java 对象(即只有一个 clojure.lang.Var
对象指向整数 5
) .
一个 Var 甚至可以指向另一个 Var 而不是一个数据值:
(def p (def five 5)) ; please don't ever do this
虽然它有效,但我想不出这样做的正当理由。
希望这个小例子能让您步入正轨:
> (defn your-handler [x] x)
#'your-handler
> (defn wrap-inc [f]
(fn [x]
(inc (f x))))
> #'wrap-inc
> (def your-app-with-var (wrap-inc #'your-handler))
#'your-app-with-var
> (def your-app-without-var (wrap-inc your-handler))
#'your-app-without-var
> (your-app-with-var 1)
2
> (your-app-without-var 1)
2
> (defn your-handler [x] 10)
#'your-handler
> (your-app-with-var 1)
11
> (your-app-without-var 1)
2
直觉是,当您在创建处理程序时使用 var 时,您实际上是在传递具有某个值的 "container",其内容可以在将来通过定义具有相同名称的 var 来更改。当您不使用 var 时(如在 your-app-without-var
中),您传递的是此 "container" 的当前值,它不能以任何方式重新定义。
已经有几个很好的答案了。只想添加此警告:
(defn f [] 10)
(defn g [] (f))
(g) ;;=> 10
(defn f [] 11)
;; -Dclojure.compiler.direct-linking=true
(g) ;;=> 10
;; -Dclojure.compiler.direct-linking=false
(g) ;;=> 11
因此,当 direct linking 启用时,通过 var 的间接调用将替换为直接静态调用。与处理程序的情况类似,但是随后 every var 调用,除非您明确引用 var,例如:
(defn g [] (#'f))
我正在阅读这本书 Web Development with Clojure 它告诉我将处理程序(定义如下)作为 Var 对象而不是函数本身传递,因为函数可以动态更改(这就是wrap-reload 确实如此)。
书上说:
"请注意,为了这个中间件,我们必须从处理程序创建一个 var 上班。这是确保包含当前的 Var 对象所必需的 返回处理函数。如果我们改用处理程序,那么应用程序会 只能看到函数的原始值,更改不会反映出来。” 我不是很明白这是什么意思,vars类似于c指针吗?
(ns ring-app.core
(:require [ring.adapter.jetty :as jetty]
[ring.util.response :as response]
[ring.middleware.reload :refer [wrap-reload]]))
(defn handler [request]
(response/response
(str "<html>/<body> your IP is: " (:remote-addr request)
"</body></html>")))
(defn wrap-nocache [handler]
(fn [request]
(-> request
handler
(assoc-in [:headers "Pragma"] "no-cache"))))
这是处理程序调用:
(defn -main []
(jetty/run-jetty
(wrap-reload (wrap-nocache (var handler)))
{:port 3001
:join? false}))
是的,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 ...)
形式引入了 不可见的 第三个元素,即 Var
。所以如果我们说
(def wilma 3)
我们现在有 3 个对象需要考虑。 wilma
是一个符号,它指向一个 Var
,它又指向值 3
。当我们的程序遇到符号wilma
时,就是求值找到Var
。同样,Var 被 求值 以产生值 3。所以它就像 C 中指针的 2 级间接寻址。因为这两个符号和 Var 是“自动求值”的,这是自动且无形地发生的,您不必考虑 Var(事实上,大多数人并没有真正意识到无形的中间步骤甚至存在)。
对于我们上面的函数 fred
,存在类似的情况,除了 Var 指向匿名函数 (fn [x] (+ x 1))
而不是像 wilma
那样的值 3
。
我们可以像这样“短路”Var 的自动求值:
> (var wilma)
#'clj.core/wilma
或
> #'wilma
#'clj.core/wilma
其中 reader 宏 #'
(引号)是调用 (var ...)
特殊形式的 shorthand 方式。请记住,像 var
这样的特殊形式是像 if
或 def
这样的内置编译器,并且是 而不是 与常规函数相同。 var
特殊形式 return 是附加到符号 wilma
的 Var 对象。 clojure REPL 使用相同的 shorthand 打印 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 将函数对象表示为字符串的方式。
对于 Web 服务器,它可以通过 var?
函数或其他方式判断提供的值是处理函数还是指向处理函数的 var。
如果您键入如下内容:
(jetty/run-jetty handler)
双重自动求值将产生传递给run-jetty
的处理函数对象。相反,如果您键入:
(jetty/run-jetty (var handler))
然后指向处理函数对象的Var
将被传递给run-jetty
。然后,run-jetty
将不得不使用 if
语句或等效语句来确定它收到了什么,如果收到 Var
而不是函数,则调用 (var-get ...)
。因此,每次通过 (var-get ...)
都会 return 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 将 SILENTLY 将其解引用到函数对象中,然后调用该函数对象与提供的参数。所以我们看到 add-3
、add-3-fn
和 add-3-var
这三个都可以工作。这就是 Jetty 中正在发生的事情。它永远不会意识到您给了它一个 Var 对象而不是一个函数,但是 Clojure 在不告诉您的情况下神奇地修补了这种不匹配。
Sidebar: Please note this only works since our "jetty" is actually the Clojure wrapper code
ring.adapter.jetty
, and not the actual Java webserver Jetty. If you tried to depend on this trick with an actual Java function instead of a Clojure wrapper, it would fail. Indeed, you must use a Clojure wrapper likeproxy
in order to pass a Clojure function to Java code.
如果您将 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
- reader宏
#'xxx
翻译成(var xxx)
- reader宏
@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.
另注
(def ...)
特殊形式 return 是它创建的 clojure.lang.Var
对象。通常,(def ...)
形式仅在 Clojure 源文件(或 REPL)的顶层使用,因此 return 值被静默丢弃。但是,也可以捕获对创建的 Var
对象的引用:
(let [p (def five 5)
q (var five)]
(is= clojure.lang.Var
(type p)
(type q))
(is= 6
(inc five)
(inc (var-get p))
(inc (deref q)))
(is (identical? p q)))
这里我们创建了一个指向数字5
的全局变量five
。 def
形式的 return 值在本地值 p
中被捕获。我们使用 (var ...)
特殊形式来获得指向同一个 Var
对象的引用 q
。
第一个测试表明p
和q
都是clojure.lang.Var
类型。中间测试显示了三种访问值 5
的方法。正如预期的那样,所有检索值 5
递增以产生 6
。最后一个测试验证 p
和 q
都指向同一个 Java 对象(即只有一个 clojure.lang.Var
对象指向整数 5
) .
一个 Var 甚至可以指向另一个 Var 而不是一个数据值:
(def p (def five 5)) ; please don't ever do this
虽然它有效,但我想不出这样做的正当理由。
希望这个小例子能让您步入正轨:
> (defn your-handler [x] x)
#'your-handler
> (defn wrap-inc [f]
(fn [x]
(inc (f x))))
> #'wrap-inc
> (def your-app-with-var (wrap-inc #'your-handler))
#'your-app-with-var
> (def your-app-without-var (wrap-inc your-handler))
#'your-app-without-var
> (your-app-with-var 1)
2
> (your-app-without-var 1)
2
> (defn your-handler [x] 10)
#'your-handler
> (your-app-with-var 1)
11
> (your-app-without-var 1)
2
直觉是,当您在创建处理程序时使用 var 时,您实际上是在传递具有某个值的 "container",其内容可以在将来通过定义具有相同名称的 var 来更改。当您不使用 var 时(如在 your-app-without-var
中),您传递的是此 "container" 的当前值,它不能以任何方式重新定义。
已经有几个很好的答案了。只想添加此警告:
(defn f [] 10)
(defn g [] (f))
(g) ;;=> 10
(defn f [] 11)
;; -Dclojure.compiler.direct-linking=true
(g) ;;=> 10
;; -Dclojure.compiler.direct-linking=false
(g) ;;=> 11
因此,当 direct linking 启用时,通过 var 的间接调用将替换为直接静态调用。与处理程序的情况类似,但是随后 every var 调用,除非您明确引用 var,例如:
(defn g [] (#'f))