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

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

问题描述

我正在阅读Clojure Web开发书,它告诉我传递处理程序(定义为波纹管)var对象而不是函数本身,因为该函数将动态更改(这是wrap-reload所做的事情)。

I am going through the clojure web development book and it tells me to pass the handler (defined bellow) var object instead of the function itself because the function will change dynamically (this is what wrap-reload does).

这本书说:

请注意,为此我们必须从处理程序中创建一个var中间件
起作用。这是确保返回包含当前
处理函数的Var对象所必需的。如果我们使用处理程序,则应用程序
只会看到原始值功能和变化将不会得到体现。
我不太明白这是什么意思,变量类似于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 如下:

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

实际上有3件事这里。首先, fred 符号。符号 fred (无引号)与关键字:fred (以前导 char)和字符串 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 :right

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 ...) 表单引入了 不可见 第三个​​元素,即变量。因此,如果我们说

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

(def wilma 3)

我们现在要考虑3个对象。 是一个符号,它指向一个 var ,它又指向值 3 。当我们的程序遇到符号 wilma 时, 已评估 会找到 var 。同样,对var进行 求值 以产生值3。因此,它就像C语言中指针的2级间接寻址。由于符号变量是自动评估的,这种情况会自动且无形地发生,并且您不必考虑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.

我们可以短路到变量的自动求值,例如:

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

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

> #'wilma
#'clj.core/wilma

其中阅读器宏# '(磅引号)是调用(var ...)特殊形式的简写方式。请记住,像 var 这样的特殊格式是内置的编译器,例如 if def ,并且 与常规函数相同。 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同样适用:

> (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中的全局指针或全局引用。

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:

  • http://clojure.org/reference/evaluation
  • http://clojure.org/reference/vars

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-3 add-3-fn的全部三个 add-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
网络服务器Jetty
。如果您尝试使用
实际Java函数而不是Clojure包装器来依赖此技巧,则它将失败。确实,必须使用Clojure包装器(如 proxy )才能将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-get deref 都使用Clojure Var

  • r eader宏#'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天全站免登陆