【问题标题】:Whats the difference between parens and brackets in "require"?“require”中的括号和括号有什么区别?
【发布时间】:2013-04-01 01:44:23
【问题描述】:

我有点困惑的是clojure require 语句中括号和括号之间的区别。我想知道是否有人可以向我解释这一点。例如,它们做同样的事情:

(ns sample.core
  (:gen-class)
  (:require clojure.set clojure.string))

 (ns sample.core
  (:gen-class)
  (:require [clojure.set] 
            [clojure.string]))

但是,这适用于 repl

(require 'clojure.string 'clojure.test)

但在 clj 文件中失败

(ns sample.core
  (:gen-class)
  (:require 'clojure.string 'clojure.test))
...
Exception in thread "main" java.lang.Exception: lib names inside prefix lists must not contain periods
    at clojure.core$load_lib.doInvoke(core.clj:5359)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    ....

而这些似乎做同样的事情:

(ns sample.core
  (:gen-class)
  (require clojure.set clojure.string))

(ns sample.core
  (:gen-class)
  (:require clojure.set clojure.string))

总的来说,我不明白这一点。我了解使用、导入和要求。但我不明白“:”以及 [] 和 '() 等中的事物之间的区别。谁能以直观的方式阐明这个话题?

【问题讨论】:

标签: clojure


【解决方案1】:

这里的问题很微妙,如果不先了解一下宏,可能很难理解。

宏操作语法的方式与函数操作值的方式相同。事实上,宏只是带有一个钩子的函数,它可以在编译时对它们进行评估。它们传递您在源代码中看到的数据文字,并自上而下进行评估。让我们创建一个具有相同主体的函数和一个宏,这样您就可以看到区别:

(defmacro print-args-m [& args]
  (print "Your args:")
  (prn args))

(defn print-args-f [& args]
  (print "Your args:")
  (prn args))

(print-args-m (+ 1 2) (str "hello" " sir!"))

; Your args: ((+ 1 2) (str "hello" " sir!"))

(print-args-f (+ 1 2) (str "hello" " sir!"))

; Your args: (3 "hello sir!")

宏被它们的返回值替换。您可以使用macroexpand检查此过程

(defmacro defmap [sym & args]
  `(def ~sym (hash-map ~@args))) ; I won't explain these crazy symbols here.
                                 ; There are plenty of good tutorials around

(macroexpand
  '(defmap people
     "Steve" {:age 53, :gender :male}
     "Agnes" {:age 7,  :gender :female}))

;  (def people
;    (clojure.core/hash-map
;      "Steve" {:age 53, :gender :male}
;      "Agnes" {:age 7, :gender :female}))

在这一点上,我应该解释一下' 导致下面的表格是quoted。这意味着编译器将读取表单,但不会执行它或尝试解析符号等。即'conj 计算为一个符号,而conj 计算为一个函数。 (eval 'conj) 等价于(eval (quote conj)) 等价于conj

考虑到这一点,要知道你不能将符号解析为命名空间,除非它以某种方式神奇地导入到你的命名空间中。这就是require 函数的作用。它接受符号并找到它们对应的命名空间,使它们在当前命名空间中可用。

让我们看看ns 宏扩展成什么:

(macroexpand
  '(ns sample.core
    (:require clojure.set clojure.string)))

;  (do
;    (clojure.core/in-ns 'sample.core)
;    (clojure.core/with-loading-context
;      (clojure.core/refer 'clojure.core)
;      (clojure.core/require 'clojure.set 'clojure.string)))

看看它是如何为我们引用符号clojure.setclojure.string 的?多么方便!但是,当您使用 require 而不是 :require 时,有什么关系呢?

(macroexpand
 '(ns sample.core
   (require clojure.set clojure.string)))

;  (do
;    (clojure.core/in-ns 'sample.core)
;    (clojure.core/with-loading-context
;      (clojure.core/refer 'clojure.core)
;      (clojure.core/require 'clojure.set 'clojure.string)))

似乎编写ns 宏的人足够好,可以让我们双向进行,因为这个结果与以前完全相同。尼托!

编辑:tvachon 只使用:require 是正确的,因为它是唯一官方支持的形式

但是括号是怎么回事?

(macroexpand
  '(ns sample.core
    (:require [clojure.set] 
              [clojure.string])))

; (do
;  (clojure.core/in-ns 'sample.core)
;  (clojure.core/with-loading-context
;   (clojure.core/refer 'clojure.core)
;   (clojure.core/require '[clojure.set] '[clojure.string])))

事实证明它们也被引用了,就像我们编写对 require 的独立调用一样。

事实证明ns 并不关心我们是否给它列表(括号)或向量(括号)来使用它。它只是将参数视为事物的序列。例如,这有效:

(ns sample.core
  [:gen-class]
  [:require [clojure.set]
            [clojure.string]])

require,正如 cmets 中的 amalloy 所指出的,对于向量和列表有不同的语义,所以不要混淆它们!

最后,为什么以下不起作用?

(ns sample.core
  (:require 'clojure.string 'clojure.test))

好吧,既然ns 为我们进行了引用,这些符号被引用了两次,这在语义上与仅被引用一次不同,也是纯粹的疯狂。

conj    ; => #<core$conj clojure.core$conj@d62a05c>
'conj   ; => conj 
''conj  ; => (quote conj)
'''conj ; => (quote (quote conj))

我希望这会有所帮助,我绝对建议学习如何编写宏。它们非常有趣。

【讨论】:

  • (:require (clojure.set) (clojure.string)) 根本不起作用。这是一个无操作,它恰好 看起来 像它一样工作,因为您选择了两个已经需要的命名空间。在一些不存在的命名空间上尝试:它默默地成功;在现存的命名空间上,它默默地什么都不做。这里使用括号表示前缀列表,如(:require (clojure set string));您提供的语法仅适用于向量。
  • 很好的答案,TDT 参考 +1,如果是这样的话
  • DJ,为什么会这样:(ns image-test.core (:gen-class) (:require (png-extract) [clojure.string :as string])) 但失败了:(ns image-test.core (:gen-class) (:require [png-extract] [clojure.string :as string]))
【解决方案2】:

TL;DR:

(ns sample.core (:gen-class) (:require clojure.set clojure.string))

(ns sample.core (:gen-class) (:需要 [clojure.set] [clojure.string]))

都很好——第二个版本只是require支持的最灵活语法的一个特例。这也可以写成:

(ns sample.core (:gen-class) (:require [clojure 设置字符串]))

一般来说,最后一种形式是此特定需求的最佳实践。


(require 'clojure.string 'clojure.test)

也适用于 clj 文件 - 试试这个:

(ns sample.core (:gen-class)) (需要'clojure.string'clojure.test)

这里的困惑是,在您损坏的示例中,您试图在 ns 宏的 :require 子句中使用“带引号的符号”。这可能不是最直观的解释,但它是如何分解的:

有两种方式需要其他模块,requirens

require 是一个接受引用形式列表的函数(需要“引用”以避免 clojure 像查找所有其他符号一样查找传递给 require 的符号)。

ns 是一个支持:require 选项的宏。它获取此选项的值,并在幕后将其转换为对require 函数的调用。您不需要引用:require 选项的值,因为ns 是一个宏,因此能够引用符号本身。

这可能还不清楚,但我建议您转向 Clojure 文档进行澄清 - 一旦您完全了解所有内容,您将对 Clojure 有一个更好的了解。

在 Clojure 源文件中,您应该始终使用 ns 子句来要求库 - require 只能在 REPL 中使用。


在你的最后两个例子中你是正确的

(ns sample.core (:gen-class) (需要 clojure.set clojure.string))

有效,但这是一个意外 - 可能是因为

(名称:要求) => “要求” (名称'要求) => “要求”

记录的语法是

(ns sample.core (:gen-class) (:require clojure.set clojure.string))

并且是唯一一个保证将来不会损坏的。

【讨论】:

  • “在 Clojure 源文件中,您应该始终使用 ns 子句来要求库 - 要求只能在 REPL 中使用。”为什么?
  • 没有技术原因 - 完全是风格和可读性之一。使用ns 始终可以确保其他程序员可以轻松查看文件需要哪些名称空间,而无需深入研究整个文件。它也更简洁一些,因为您不需要手动转义表单。
猜你喜欢
  • 2021-05-16
  • 2014-04-18
  • 2016-06-26
  • 2014-09-30
  • 1970-01-01
  • 2012-02-12
  • 2021-05-12
  • 1970-01-01
相关资源
最近更新 更多