Clojure 块封装与局部封装-let

Clojure 块封装与局部封装-let,clojure,let,Clojure,Let,当我有一个独立于参数的函数的相关数据时,我什么时候应该支持块封装而不是局部封装 我应该在什么时候使用: (let [hello "Hello "] (defn do-greet "Print a greeting." [name] (println (str hello name)))) 与: (defn do-greet "Print a greeting." [name] (let [hello "Hello "] (println (str

当我有一个独立于参数的函数的相关数据时,我什么时候应该支持块封装而不是局部封装

我应该在什么时候使用:

(let [hello "Hello "]
  (defn do-greet
    "Print a greeting."
    [name]
    (println (str hello name))))
与:

(defn do-greet
  "Print a greeting."
  [name]
  (let [hello "Hello "]
    (println (str hello name))))
肯定是这样的:

(defn do-greet
  "Print a greeting."
  [name]
  (let [hello "Hello "]
    (println (str hello name))))

如果
hello
仅在该单个函数中使用,则将
let
放在函数本身中更有意义。如果您打算在多个函数中使用
hello
,那么将
let
放在这些函数之外并围绕这些函数进行包装是很有意义的。

这是一种风格选择,可能至少要取决于计算值的成本。考虑一下:

(defn nth-prime [n] ...)

(defn f [x]
  (let [factor (nth-prime 10000)]
    (* x factor)))

(let [factor (nth-prime 10000)]
  (defn g [x]
    (* x factor)))

每次调用
f
时重新计算一个昂贵的常量是浪费的,而
g
使用了一种简单的技术来避免这样做。

如果您想像在词汇范围的代码块中使用静态常量一样使用值,前者是一个合理的选择。通常,您会在以下情况下执行此操作:

  • 该值的计算代价很高,并且在加载命名空间时只需执行一次
  • 该值是真正的常量,即不会在函数调用期间更改
  • 该值将在多个函数定义中使用(即,在let块中放置多个defn)
  • (可能?)因为您希望在宏扩展中使用该值,而将let嵌入宏扩展本身将增加不必要的复杂性
在大多数其他情况下,后一种版本可能是首选,因为以下几个原因:

  • 它更地道
  • 它允许函数定义处于顶层,这有利于代码的可读性/理解性
  • 它允许值根据不同的函数调用而变化
  • 它更好地反映了在本地上下文中使用该值的意图

如果这两者在运行时不相等,我认为我们不应该仅仅根据风格来决定。相反,假设绑定是读取一个静态资源
(让[hello(slurp(clojure.java.io/resource“hello”)])…
。我把我的问题留得更笼统一些,因为我不知道这些形式实际上是如何评估的,以及相应的权衡是什么。在某些情况下,它与风格没有任何关系。如果需要该值在两个不同的函数中可用,则必须使该值在这些函数的作用域之上可用。另外,请看@amalloy的回答。如果说运行时行为(性能)有所不同,我认为这不是一种风格选择。只是因为在提问者的例子中,实际上没有什么不同,这是风格上的。我喜欢这样,你涵盖了更多的要点。我不确定您的宏扩展点是否有效,因为您可以将let环绕扩展,但仍在函数内部。为了便于阅读,我建议将文体选择与语义差异分开。(值可以根据不同的函数调用而变化)。