我正在阅读Clojure Web开发书,它告诉我传递处理程序(定义为波纹管)var对象而不是函数本身,因为该函数将动态更改(这是wrap-reload所做的事情)。
这本书说:
“请注意,我们必须从处理程序创建一个var才能使中间件正常工作。这是确保返回包含当前处理程序功能的Var对象的必要条件。如果我们使用处理程序,则应用只会看到该功能的原始值和更改将不会得到体现。” 我不太明白这是什么意思,var和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 =左,2 =右,我们只使用关键字:left
和即可:right
。
就像在Java或C中一样,符号旨在指向事物。
(let [x 1
y (+ x 1) ]
(println y))
;=> 2
然后x
指向值1,y
指向值2,我们看到打印的结果。
的(def ...)
形式引入了一个隐形第三元件,所述变种 所以如果我们说
(def wilma 3)
我们现在要考虑3个对象。wilma
是一个符号,它指向a var
,而后者又指向value 3
。当我们的程序遇到符号时wilma
,将对其进行评估以找到var
。同样,对var进行求值以得出值3。因此,就像C语言中的指针的2级间接寻址。由于symbol和var都是“自动求值”的,因此这是自动,无形的,并且您不需要必须考虑var(实际上,大多数人并不真正意识到甚至存在不可见的中间步骤)。
对于fred
上面的函数,除了var指向匿名函数(fn [x] (+ x 1))
而不是3
诸如的值之外,存在类似的情况wilma
。
我们可以像这样“短路” var的自动求值:
> (var wilma)
#'clj.core/wilma
要么
> #'wilma
#'clj.core/wilma
其中的reader宏#'
(磅引号)是调用(var ...)
特殊格式的简写方式。请记住,就像一种特殊形式var
是内置的编译器像if
或者def
,是不一样的常规功能。的var
特殊形式,返回附连到该符号的功对象wilma
。clojure REPLVar
使用相同的速记来打印对象,因此两个结果看起来都相同。
一旦有了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-get ...)
它(如果收到了)Var
而不是函数。因此,每次通过(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将以静默方式将其取消引用到函数对象中,然后使用提供的args调用该函数对象。所以我们看到,所有这三个add-3
,add-3-fn
而add-3-var
将工作。这就是在码头发生的事情。它从来没有意识到您给了它一个Var对象而不是一个函数,但是Clojure神奇地修补了这种不匹配,而没有告诉您。
补充工具栏:请注意,这仅适用于我们的“码头”实际上是Clojure包装器代码
ring.adapter.jetty
,而不是实际的Java Web服务器码头。如果您尝试使用实际Java函数而不是Clojure包装器来依赖此技巧,则它将失败。确实,您必须像使用Clojure包装器一样proxy
,才能将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.
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句