Clojure中的fn和defn有什么区别?

9

以下是Clojure中编写函数的两种方式:

(defn foo [a b] (+ a b))
(fn foo [a b] (+ a b))

我可以这样调用它们:
在“defn”的情况下
(foo 1 2)

在“fn”情况下
((fn foo [a b] (+ a b)) 1 2)

'fn'似乎不会将其可选名称插入当前范围,在此期间, 'defn'似乎确实会这样做。除此之外,还有其他区别或创建函数的原因吗?我们为什么不能仅使用以下方式使用'fn':

(fn foo [a b] (+ a b))
(foo 1 2)
2个回答

18

defn基本上被定义为*:

(defmacro defn [name args & body]
  `(def ~name (fn ~args ~@body)))

换句话说,您可以“基本上”编写:

(defn my-func [a]
  (stuff a))

作为*:

(def my-func
  (fn [a] (stuff a)))

仅使用fn会创建一个匿名函数,它本身不与任何外部符号绑定。必须使用letdef将其绑定才能在其外部引用。

通过使用deffn来定义defn,可以分离绑定函数到符号(以及所有相关的复杂性)和处理函数行为的责任。

当您为fn提供名称时,它无法在函数外部被引用,但可以用于引用自身以创建递归的匿名函数:

(fn my-func [n] (my-func (inc n))

并且,它会给这个函数一个稍微好一点的名字,以便在堆栈跟踪中更容易地显示,从而方便调试:

(defn my-func []
  ((fn my-inner-func [] (/ 1 0))))
=> #'digital-rain.core/my-func

(my-func)
java.lang.ArithmeticException: Divide by zero
    at clojure.lang.Numbers.divide(Numbers.java:158)
    at clojure.lang.Numbers.divide(Numbers.java:3808)
    at digital_rain.core$my_func$my_inner_func__2320.invoke(form-init1838550899342340522.clj:2)
    at digital_rain.core$my_func.invokeStatic(form-init1838550899342340522.clj:2)
    at digital_rain.core$my_func.invoke(form-init1838550899342340522.clj:1)

* 这些是严重低估和有点误导,但它们简化了事情。

实际上,defn 并非使用 defmacro 定义;相反,defmacro 实际上是使用 defn 定义的。defn 还添加了一些好东西,比如前/后条件检查,文档和其他元信息;但这对这里并不那么相关。我建议查看其源代码以进行更深入的了解;虽然它相当复杂。 clojure.core 的基本核心可能有点令人生畏。


1
而在(fn foo [a b] (+ a b))的情况下,这仍然是匿名函数,如果需要递归调用匿名函数本身,则可以在函数体内使用foo。 - Bojan Horvat
1
@BojanHorvat 是的,我想我最初是打算加上一些关于那个的说明,但它没有被采用。我现在会添加一些内容。 - Carcigenicate

0

fn: 定义了一个函数对象,但没有给它命名。当您想创建匿名类时,这是很好的选择。如果要给它命名,您需要使用 def:

(def greet (fn [name] (str "Hello, " name)))

另一方面,defn 定义一个函数并给它命名:
(defn greet [name] (str "Hello, " name))

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