Clojure中def和defn的区别,针对无参数函数

54

我已经用Clojure语言写了一个程序,但其中一些函数没有参数。将这些函数用"def"定义与使用"defn"定义但不带参数的方式相比,有哪些优点呢?


6
你的意思是 (def x (fn [] (something))) 还是 (def x (something))?它们完全不同,而被接受的答案仅对第二种解释正确。 - Charles Duffy
1
我猜是第二种解释,但你说得很好! - yazzapps.com
4个回答

131
(def t0 (System/currentTimeMillis))
(defn t1 [] (System/currentTimeMillis))
(t1)
;; => 1318408717941
t0
;; => 1318408644243
t0
;; => 1318408644243
(t1)
;; => 1318408719361

14
谢谢。这个代码示例胜过千言万语的描述。 - user693960
19
为了不让任何人错过这个关键点:正如Roman Bataev的评论所暗示的那样,(def t2 (fn [] (System/currentTimeMillis)))将使t2的行为类似于t1 - Mars
2
def将一个值绑定到一个名称,defn定义一个函数并将其绑定到一个名称。更多详情请参见defn vs fn - mbigras

88

def仅评估一次,而defn(带参数或不带参数)每次调用时都会评估(执行)。因此,如果您的函数始终返回相同的值,则可以将它们更改为def,否则不能。


啊,我不知道这个,你救了我免受一些严重的 bug!那么如果有一个访问引用的 def,它永远不会被重新计算吗?如果文件重新加载会怎样? - yazzapps.com
1
当文件重新加载时,def会被重新评估。 - Abhinav Sarkar
3
我认为称 defn 表达式被重新评估是错误的。正如其他回答所指出的那样,defn 在宏展开时解析为 def,它们确实只在文件加载时被评估一次。 - skuro
7
@MattFenwick所表达的意思是,defn和每个def一样只会被评估一次,这里它定义了一个函数。然而,所定义函数的主体可以被评估任意次数。我认为这并没有什么用处,但他说的也不是错的。 - amalloy
1
我读到的问题是(def x (fn [] (do-something-here))),而你似乎将其解释为(defn x [] (do-something-here))。如果提问者能够具体说明哪种解释是正确的,那就好了。 - Charles Duffy
Charles做出了一个重要的区分,但原帖作者只是这里信息的一个消费者。考虑到可能会引起混淆,我认为这个现有形式的回答并不是一个有效的被接受的答案。下面Siefca的回答特别解决了这种潜在的混淆。 - ctpenrose

50

1
这是最简洁的答案!如果有人在谷歌上搜索“defn vs def”,我希望他能快速找到这个帖子。谢谢! - Aleksandar

19
< p > def 特殊形式创建一个由其第一个参数作为标识符的 Var 对象。通过将给定的符号与名为命名空间的映射中的 Var 关联来创建标识符。

Var 持有对某些值的引用,这可以表示为(其中之一):

  • 作为常量表达式,它始终计算为自己的值
(def x 1)
x
;; => 1             ; x holds a reference to a number 1
  • 作为函数形式,最初需要评估其结果值
(def x (+ 2 2))
x
;; => 4             ; x holds a reference to a number 4
  • 作为Java方法形式,首先被计算为其结果值
(def x (System/currentTimeMillis))
x
;; => 1417811438904 ; x holds a reference to a number 1417811438904
x
;; => 1417811438904 ; still the same number!
  • 作为lambda形式(匿名函数),首先被评估为函数对象
(def x (fn [] (System/currentTimeMillis)))
x
;; => #<user$x user$x@4c2b1826>
(x)                             ; function form, function evaluated
;; => 1417811438904
(x)                             ; function form, function evaluated
;; => 1417812565866
在上述情况下有一个简单的规则。在def特殊形式的情况下,作为其第二个参数的S表达式将会递归地求值并且在创建绑定之前进行求值,因此结果Var将绑定到该求值的结果。
即使fn也会被评估,但其结果值是一个包含代码的函数对象。该代码将会每次调用函数时执行(并被求值)。这就是为什么会有不同的结果。 defn宏就像def一样,但在内部它创建了一个匿名函数,然后将一个 Var 对象绑定到它上面。它的第二个参数成为这个函数的主体,并且不是以"常规"方式进行评估。也可以这样说,它被评估为lambda形式-评估的结果是函数对象,而不是某个瞬间计算的结果。
所以写:
(defn fun [] 1)

等同于:

(def fun (fn [] 1))

1
优秀的回答。应该被接受为答案。 - Ugur

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