Clojure中的let和def有什么区别?

40

我想在Clojure程序中创建Java Scanner类的本地实例。为什么这样不起作用:

; gives me:  count not supported on this type: Symbol 
(let s (new Scanner "a b c"))

但它允许我像这样创建一个全局实例:

(def s (new Scanner "a b c"))

我原以为唯一的区别是作用域,但显然不是这样。 letdef之间有什么区别?

6个回答

58

问题在于你错误地使用了 let

let 的用法如下:

(let [identifier (expr)])

因此,你的示例应该像这样:

(let [s (Scanner. "a b c")]
  (exprs))

你只能在 let 的作用域内(即括号之间)使用用 let 声明的词法绑定。let 只会创建一组词法绑定。我使用 def 来创建一个全局绑定,而使用 let 来绑定我想要它仅存在于 let 作用域中的内容,这样可以保持代码简洁。它们都有各自的用途。

注意:(Class.) 和 (new Class) 是相同的,只是语法糖不同。


35

LET不是"在当前范围内进行词法绑定",而是"创建一个新的词法作用域,并将下面的绑定放入其中"。

(let [s (foo whatever)]
  ;; 这里 s 被绑定了
  )
;; 但这里没有被绑定
(def s (foo whatever))
;; s 在这里被绑定

我明白了,有点像C#的using或Python的with,但没有任何销毁(考虑到不可变状态,这样做有点愚蠢)。 - Jason Baker

14

简化版: def 用于全局常量,let 用于局部变量。


3
不是这样简单,恰恰是因为这个原因导致提问者困惑。LET创建一个具有词法绑定的新块,而DEF只创建一个新的“全局”绑定。 - Svante
听起来不太对。def总是绑定到一个变量上,因此不是常量。命名空间绑定也可以更改,因此它们甚至不是常量。let是一个常量,它不能被更改或重新绑定。所以至少我会说:“def用于全局变量,let用于本地常量。” - Didier A.

12

正确的语法:

(let [s (Scanner. "a b c")] ...)

4
你可以把let看作是使用fn创建一个新的词法作用域,然后立即应用它的语法糖。
(let [a 3 b 7] (* a b))  ; 21
; vs.
((fn [a b] (* a b)) 3 7) ; 21

所以,您可以通过简单的宏和 fn 实现 let

(defmacro fnlet [bindings & body]
  ((fn [pairs]
    `((fn [~@(map first pairs)] ~@body) ~@(map last pairs)))
   (partition 2 bindings)))

(fnlet [a 3 b 7] (* a b)) ; 21

4

它们的语法不同,即使它们的含义是相关的。

let接受一个绑定(名称-值对)列表,后跟要在该绑定上下文中评估的表达式。

def只需要一个绑定,而不是列表,并将其添加到全局上下文中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接