何时使用 Var 而不是函数? [英] When to use a Var instead of a function?

查看:33
本文介绍了何时使用 Var 而不是函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在阅读这本书 使用 Clojure 进行 Web 开发 并且它告诉我将处理程序(定义如下)作为 Var 对象而不是作为函数本身传递,因为函数可以动态更改(这就是 wrap-reload 所做的).

I am going through the book Web Development with Clojure and it tells me to pass the handler (defined bellow) as a Var object instead of as the function itself, since the function can then change dynamically (this is what wrap-reload does).

书中说:

"请注意,为了这个中间件,我们必须从处理程序创建一个 var去工作.这是必要的,以确保包含当前的 Var 对象返回处理函数.如果我们改用处理程序,那么应用程序将只看到函数的原始值,更改不会反映."我真的不明白这是什么意思,vars 类似于 c 指针吗?

"Note that we have to create a var from the handler in order for this middleware to work. This is necessary to ensure that the Var object containing the current handler function is returned. If we used the handler instead then the app would only see the original value of the function and changes would not be reflected." I don't really understand what this means, are vars similar to c pointers?

(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 指针.这没有很好的记录.

Yes, a Var in Clojure is similar to a C pointer. This is poorly documented.

假设您创建一个函数 fred 如下:

Suppose you create a function fred as follows:

(defn fred [x] (+ x 1))

这里实际上有 3 件事.首先,fred 是一个符号.符号 fred(无引号)与关键字 :fred(由前导 : 字符标记)和字符串 fred"(两端用双引号标记).对Clojure来说,每一个都由4个字符组成;即关键字的冒号和字符串的双引号都不包含在它们的长度或组成中:

There are actually 3 things here. Firstly, fred is a symbol. There is a difference between a symbol fred (no quotes) and the keyword :fred (marked by the leading : char) and the string "fred" (marked by a double-quote at both ends). To Clojure, each of them is composed of 4 characters; i.e. neither the colon of the keyword nor the double-quotes of the string are included in their length or composition:

> (name 'fred)
"fred"
> (name :fred)
"fred"
> (name "fred")
"fred"

唯一的区别是它们的解释方式.字符串用于表示任何类型的用户数据.关键字旨在以可读的形式表示程序的控制信息(与 1=left、2=right 等魔术数字"相反,我们只使用关键字 :left>:对.

The only difference is how they are interpreted. A string is meant to represent user data of any sort. A keyword is meant to represent control information for the program, in a readable form (as opposed to "magic numbers" like 1=left, 2=right, we just use keywords :left and :right.

符号用于指向事物,就像在 Java 或 C 中一样.如果我们说

A symbol is meant to point to things, just like in Java or C. If we say

(let [x 1
      y (+ x 1) ]
  (println y))
;=> 2

然后x指向值1,y指向值2,我们看到打印的结果.

then x points to the value 1, y points to the value 2, and we see the result printed.

(def ...) 表单引入了一个 不可见 第三个元素,即 var.所以如果我们说

the (def ...) form introduces an invisible third element, the var. So if we say

(def wilma 3)

我们现在需要考虑 3 个对象.wilma 是一个符号,它指向一个 var,而后者又指向值 3.当我们的程序遇到符号 wilma 时,它会求值以找到 var.同样,var 被求值以产生值 3.所以它就像 C 中指针的 2 级间接.因为符号 var 是自动评估的",这会自动且不可见地发生,您不必考虑 var(实际上,大多数人甚至没有真正意识到不可见的中间步骤甚至存在).

we now have 3 objects to consider. wilma is a symbol, which points to a var, which in turn points to the value 3. When our program encounters the symbol wilma, it is evaluated to find the var. Likewise, the var is evaluated to yield the value 3. So it is like a 2-level indirection of pointers in C. Since both the symbol and the var are "auto-evaluated", this happens automatically and invisibly and you don't have to think about the var (indeed, most people aren't really aware the invisible middle step even exists).

对于我们上面的函数fred,存在类似的情况,除了var指向匿名函数(fn [x] (+ x 1)) 而不是值 3 就像 wilma 一样.

For our function fred above, a similar situation exists, except the var points to the anonymous function (fn [x] (+ x 1)) instead of the value 3 like with wilma.

我们可以短路"var 的自动评估,如:

We can "short-circuit" the auto-evaluation of the var like:

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

> #'wilma
#'clj.core/wilma

阅读器宏 #'(磅引号)是调用 (var ...) 特殊形式的简写方式.请记住,像 var 这样的特殊形式是编译器内置的,如 ifdefnot 与常规函数相同.var 特殊形式返回附加到符号 wilma 的 Var 对象.clojure REPL 使用相同的速记打印 Var 对象,因此两个结果看起来相同.

where the reader macro #' (pound-quote) is a shorthand way of calling the (var ...) special form. Keep in mind that a special form like var is a compiler built-in like if or def, and is not the same as a regular function. The var special form returns the Var object attached to the symbol wilma. The clojure REPL prints the Var object using the same shorthand, so both results look the same.

一旦我们有了 Var 对象,自动求值就会被禁用:

Once we have the Var object, auto-evaluation is disabled:

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

如果要获取wilma指向的值,需要使用var-get:

If we want to get to the value that wilma points to, we need to use var-get:

> (var-get (var wilma))
3
> (var-get    #'wilma)
3

同样的事情适用于 fred:

The same thing works for fred:

> (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 将函数对象表示为字符串的方式.

where the #object[clj.core$fred ...] stuff is Clojure's way of representing a function object as a string.

对于 Web 服务器,它可以通过 var? 函数或其他方式判断提供的值是处理程序函数还是指向处理程序函数的 var.

With regard to the web server, it can tell via the var? function or otherwise if the supplied value is the handler function or the var which points to the handler function.

如果您输入以下内容:

(jetty/run-jetty handler)

双重自动评估将产生处理函数对象,该对象被传递给run-jetty.相反,如果您键入:

the double auto-evaluation will yield the handler function object, which is passed to run-jetty. If, instead, you type:

(jetty/run-jetty (var handler))

然后将指向处理函数对象的Var 传递给run-jetty.然后,run-jetty 将不得不使用 if 语句或等效语句来确定它收到了什么,并调用 (var-get ...) 如果它收到的是 Var 而不是函数.因此,每次通过 (var-get ...) 都会返回 Var 当前指向的对象.所以,Var 就像 C 语言中的一个全局指针,或者一个全局引用".Java 中的变量.

then the Var which points to the handler function object will be passed to run-jetty. Then, run-jetty will have to use an if statement or equivalent to determine what it has received, and call (var-get ...) if it has received a Var instead of a function. Thus, each time through (var-get ...) will return the object to which the Var currently points. So, the Var acts like a global pointer in C, or a global "reference" variable in Java.

如果您将函数对象传递给 run-jetty,它会保存一个本地指针";指向函数对象,外界无法改变局部指针所指的内容.

If you pass a function object to run-jetty, it saves a "local pointer" to the function object and there is no way for the outside world to change what the local pointer refers to.

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

You can find more details here:

正如OlegTheCat 所指出的,Clojure 还有一个关于指向Clojure 函数的Var 对象的技巧.考虑一个简单的函数:

As OlegTheCat has pointed out, Clojure has yet another trick up its sleeve regarding Var objects that point to Clojure functions. Consider a simple function:

(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-3add-3-fnadd-3-var 这三个都可以工作.这就是 Jetty 正在发生的事情.它永远不会意识到你给了它一个 Var 对象而不是一个函数,但 Clojure 神奇地修补了这个不匹配而不告诉你.

If we treat a Var object as a function, Clojure will SILENTLY deref it into the function object, then invoke that function object with the supplied args. So we see that all three of add-3, add-3-fn and add-3-var will work. This is what is occurring in Jetty. It never realizes that you have given it a Var object instead of a function, but Clojure magically patches up that mismatch without telling you.

侧边栏:请注意,这仅适用于我们的码头"实际上是Clojure 包装器代码 ring.adapter.jetty,而不是实际的 Java网络服务器码头.如果你试图依赖这个技巧实际的 Java 函数而不是 Clojure 包装器,它会失败.实际上,您必须使用像 proxy 这样的 Clojure 包装器,以便将 Clojure 函数传递给 Java 代码.

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 对象用作函数以外的任何东西,你就没有这样的守护天使来拯救你:

You have no such guardian angel to save you if you use the Var object as anything other than a function:

  (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 对象,你就靠你自己了.

So, if you are expecting a function and someone gives you a Var object that points to a function, you are OK since Clojure silently fixes the problem. If you are expecting anything other than a function and someone gives you a Var object that points to that thing, you are on your own.

考虑这个辅助函数:

(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))

现在你可以安全地使用你得到的东西:

Now you can safely use the thing you were given:

(is= 42 (+ 39 (unvar wilma))
        (+ 39 (unvar wilma-long))
        (+ 39 (unvar wilma-var)))


附录

请注意,有三个可能混淆问题的二元性:


Appendix

Notice that there are three dualities that can confuse the issue:

  • var-getderef 对 Clojure Var
  • 做同样的事情
  • 阅读器宏#'xxx被翻译成(var xxx)
  • 阅读器宏@xxx被翻译成(deref xxx)
  • Both var-get and deref do the same thing with a Clojure Var
  • The reader macro #'xxx is translated into (var xxx)
  • The reader macro @xxx is translated into (deref xxx)

所以我们有(令人困惑!)很多方法来做同样的事情:

So we have (confusingly!) many ways of doing the same thing:

(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.

这篇关于何时使用 Var 而不是函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆