何时使用Var而不是函数?

时间:2016-09-17 18:47:57

标签: web clojure var

我正在阅读clojure web开发书,它告诉我传递处理程序(定义为bellow)var对象而不是函数本身,因为函数将动态更改(这就是wrap-reload所做的)。

这本书说:

"请注意,我们必须从处理程序创建一个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}))

3 个答案:

答案 0 :(得分:16)

是的,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。 / p>

符号意味着指向事物,就像在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))而不是像3一样的值wilma

我们可以将var的自动评估“短路”:

> (var wilma)
#'clj.core/wilma

> #'wilma
#'clj.core/wilma

其中阅读器宏#'(pound-quote)是调用(var ...)特殊表单的简写方式。请记住,像var这样的特殊表单是内置的编译器,如'if'或'def',并且与常规函数不同。 var特殊表单会返回附加到符号var的{​​{1}}对象。 clojure REPL使用相同的速记打印wilma对象,因此两个结果看起来都一样。

一旦我们有了var对象,就会禁用自动评估:

var

如果我们想要达到> (println (var wilma)) #'clj.core/wilma 指向的值,我们需要使用wilma

var-get

同样的事情适用于fred:

> (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"] 的东西是Clojure将函数对象表示为字符串的方式。

关于Web服务器,它可以通过#object[clj.core$fred ...]函数或其他方式告知提供的值是处理函数还是指向处理函数的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的作用类似于C中的全局指针,或者是Java中的全局“引用”变量。

如果将函数对象传递给var,它会将“本地指针”保存到函数对象中,并且外部世界无法更改本地指针所指的内容。

您可以在此处找到更多详细信息:

答案 1 :(得分:8)

希望这个小例子能让你走上正轨:

> (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时,实际上是在传递一个&#34;容器&#34;通过定义具有相同名称的var,可以在某种程度上改变其内容。如果你不使用var(比如在your-app-without-var中),你传递的是#&#34; container&#34;的当前值,不能以任何方式重新定义。

答案 2 :(得分:7)

已经有几个好的答案。只想添加这个警告:

(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的间接替换为直接静态调用。与处理程序的情况类似,但随后使用每个 var调用,除非您明确引用var,例如:

(defn g [] (#'f))