什么时候使用Var而不是函数?

莫雷克斯

我正在阅读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级间接寻址。由于symbolvar都是“自动求值”的,因此这是自动,无形的,并且您不需要必须考虑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特殊形式,返回附连到该符号的功对象wilmaclojure 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-3add-3-fnadd-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-getderefClojure做相同的事情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] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

斯威夫特:什么时候应该使用“ var”而不是“ let”?

在Python中,什么时候应该使用函数而不是方法?

在Python中,什么时候应该使用函数而不是方法?

什么时候在Go中使用var或:=?

什么时候使用mapc而不是mapcar?

什么时候使用reduce()而不是sum()?

JVM什么时候使用内在函数

构造函数与preStart:什么时候使用?

什么时候应该使用iter函数?

什么时候使用表值函数?

Flink 什么时候使用 CoProcess 函数?

什么时候应该使用invokeCFClientFunction()函数?

什么时候使用()调用函数?

什么时候在函数中使用 return ?

什么时候应该使用函数?

什么时候在Go中使用函数表达式而不是函数声明?

什么时候在C ++中使用函数引用而不是函数指针?

什么时候通用函数不是通用函数?

什么时候应该使用 var,什么时候应该使用 string、ulong、int 等

什么时候应该在Z3中使用函数而不是变量?

什么时候应该使用动态内存分配函数而不是直接变量声明?

在CSS中,什么时候/应该何时使用`url()`函数而不是字符串?

什么时候应该使用存储过程而不是SQL Server 2008中的函数?

什么时候应该使用加,减,乘函数而不是Kotlin中的运算符?

什么时候使用内联函数,什么时候不使用内联函数?

什么时候全局函数不是可调用的?

什么时候不是str?

什么时候使用$ scope?何时在AngularJS中使用var?

匕首2:什么时候使用构造函数注入,什么时候使用字段注入?