Clojure 中的匿名函数

Anonymous function in Clojure

也许这个问题听起来很荒谬,但对我来说仍然不完全清楚匿名函数的 # 应该在哪里的区别。例如在这个例子中我过滤了一个正数的除数:

(filter #(zero? (mod 6 %)) (range 1 (inc 6)))  ;;=> (1 2 3 6)

但是将 # 放在 (mod 6 %) 之前会导致错误。在这样的上下文中我的匿名函数开始的地方是否有规则,为什么 # 应该在 (zero? ... 之前?

Clojure 的 filter 函数接受一个或两个参数;无论哪种方式,第一个参数必须是 函数 。所以没有 "rule" 定义匿名函数的地方,只要最终 filter 的第一个参数是一个函数即可。

但是,在这种情况下,zero? 不是 return 函数,因此 (zero? #(mod 6 %)) 会导致 filter 抛出错误。而且,事实上,(zero? #(mod 6 %) 也没有意义,因为 zero? 没有将函数作为参数。

这显示了 #(...) 语法如何只是 shorthand for (fn [x] ...):

(defn divides-6 [arg]
  (zero? (mod 6 arg)))

(println  (filter divides-6                   (range 1 10))) ; normal function
(println  (filter (fn [x] (zero? (mod 6 x)))  (range 1 10))) ; anonymous function
(println  (filter        #(zero? (mod 6 %))   (range 1 10))) ; shorthand version

;=> (1 2 3 6)
;=> (1 2 3 6)
;=> (1 2 3 6)

使用 defn 只是 shorthand 用于 (def divides-6 (fn [x] ...))(即 deffn 部分组合成 defn 以节省很少打字)。如果我们只打算使用该函数一次,则不需要定义全局名称 divides-6。我们可以在将要使用的地方定义内联函数。 #(...) 语法只是一个 shorthand 版本,如示例所示。

注意表格#(...)的全称是"anonymous function literal"。您可能还会看到它称为 "function reader macro" 或简称为 "function macro"。语法 (fn [x] ...) 称为 "function special form".

filter 有两个参数:

  • 一个谓词(一个过滤器,它是一个函数),
  • 一个合集

所以,简单来说:

(defn my-predicate [x]
 (zero? (mod 6 x)))

(def my-collection
 (range 1 (inc 6)))

(filter 
 my-filter 
 my-collection)

# 是一个 clojure 宏,或者可以为您预处理和重组代码的东西。我们可以看到带有 macroexpand-1 的宏的结果:

 (macroexpand-1 '#(zero? (mod 6 %)))
 ; (fn* [p1__4777#] (zero? (mod 6 p1__4777#)))

或更易读的代码:

 (fn* [x] 
   (zero? 
     (mod 6 x))

对于集合的单个值,比如3,我们可以应用上面的函数:

 ( (fn* [x] 
     (zero? 
       (mod 6 x))) 
   3)
 ; true

然后回到我们代码的#版本,函数的输入参数隐式为%,所以:

 ( 
   #(zero? (mod 6 %))
   3) 
 ; true

最后,回到原来的函数,您会明白为什么 # 需要成为定义过滤函数谓词的函数:

 (filter 
   #(zero? (mod 6 %)) 
   (range 1 (inc 6))) 
 ; (1 2 3 6)