匿名函数 #

一、匿名函数简介 #

匿名函数是没有名字的函数,常用于一次性操作或作为高阶函数的参数。Clojure提供了多种创建匿名函数的方式。

1.1 为什么需要匿名函数 #

clojure
(map (fn [x] (* x x)) [1 2 3 4 5])

(map #(* % %) [1 2 3 4 5])

匿名函数让代码更简洁,避免为简单操作定义命名函数。

二、fn形式 #

2.1 基本语法 #

clojure
(fn [params] body)

(fn [x] (* x x))

((fn [x] (* x x)) 5)

2.2 多参数 #

clojure
(fn [a b] (+ a b))

((fn [a b] (+ a b)) 3 4)

2.3 可变参数 #

clojure
(fn [x & rest]
  (cons x rest))

((fn [x & rest] (cons x rest)) 1 2 3 4 5)

2.4 多参数列表 #

clojure
(fn
  ([x] x)
  ([x y] (+ x y)))

(def add (fn
           ([x] x)
           ([x y] (+ x y))))

(add 5)

(add 3 4)

2.5 命名匿名函数 #

clojure
(fn factorial [n]
  (if (<= n 1)
    1
    (* n (factorial (dec n)))))

((fn factorial [n]
   (if (<= n 1)
     1
     (* n (factorial (dec n)))))
 5)

三、#()语法 #

3.1 基本语法 #

clojure
#(* % %)

(#(* % %) 5)

(map #(* % %) [1 2 3 4 5])

3.2 参数占位符 #

clojure
#(+ % 1)

#(+ %1 %2)

#(+ %1 %2 %3)

#(* %1 %1)
占位符 含义
% 第一个参数(等同于 %1
%1 第一个参数
%2 第二个参数
%n 第n个参数
%& 剩余参数

3.3 多参数示例 #

clojure
(#(+ %1 %2) 3 4)

(#(str %1 "-" %2) "a" "b")

(map #(vector %1 %2) [1 2 3] [:a :b :c])

3.4 可变参数 #

clojure
(#(apply + %&) 1 2 3 4 5)

(#(vector %1 %&) 1 2 3 4 5)

3.5 嵌套表达式 #

clojure
#(str "Hello, " % "!")

#(let [x (* % 2)]
   (str "Double: " x))

#(if (even? %)
   "even"
   "odd")

四、闭包 #

4.1 什么是闭包 #

闭包是捕获外部作用域变量的函数:

clojure
(defn make-adder [n]
  (fn [x] (+ x n)))

(def add5 (make-adder 5))
(def add10 (make-adder 10))

(add5 3)

(add10 3)

4.2 捕获多个变量 #

clojure
(defn make-multiplier [factor offset]
  (fn [x]
    (+ (* x factor) offset)))

(def calc (make-multiplier 2 10))

(calc 5)

4.3 状态封装 #

clojure
(defn make-counter []
  (let [count (atom 0)]
    (fn []
      (swap! count inc))))

(def counter (make-counter))

(counter)

(counter)

(counter)

4.4 配置闭包 #

clojure
(defn make-logger [prefix]
  (fn [message]
    (println (str prefix ": " message))))

(def error-log (make-logger "ERROR"))
(def info-log (make-logger "INFO"))

(error-log "Something went wrong")
(info-log "Process completed")

五、词法作用域 #

5.1 作用域规则 #

Clojure使用词法作用域,函数捕获定义时的环境:

clojure
(def x 10)

(defn outer []
  (let [x 20]
    (fn [] x)))

((outer))

(def inner (outer))

(inner)

5.2 嵌套作用域 #

clojure
(defn make-nested [a]
  (fn [b]
    (fn [c]
      (+ a b c))))

(def f1 (make-nested 1))
(def f2 (f1 2))

(f2 3)

5.3 let绑定 #

clojure
(let [x 10
      f (fn [y] (+ x y))]
  (f 5))

(let [x 10]
  (let [f (fn [y] (+ x y))
        x 20]
    (f 5)))

六、匿名函数应用 #

6.1 作为高阶函数参数 #

clojure
(map #(inc %) [1 2 3])

(filter #(> % 3) [1 2 3 4 5])

(reduce #(+ %1 %2) 0 [1 2 3])

(sort-by #(:age %) [{:name "Bob" :age 25}
                    {:name "Alice" :age 30}])

6.2 条件处理 #

clojure
(filter #(and (even? %) (> % 2)) [1 2 3 4 5 6])

(remove #(or (nil? %) (empty? %)) [1 nil "" 2 "hello"])

6.3 数据转换 #

clojure
(map #(assoc % :processed true) [{:id 1} {:id 2}])

(map #(update % :count inc) [{:count 1} {:count 2}])

6.4 回调函数 #

clojure
(defn process-with-callback [data callback]
  (callback (map inc data)))

(process-with-callback [1 2 3] #(reduce + %))

七、fn vs #() #

7.1 使用场景 #

使用#()当

  • 函数体简单
  • 单行表达式
  • 作为高阶函数参数
clojure
(map #(* % 2) [1 2 3])

(filter #(> % 5) [1 2 3 4 5 6 7])

使用fn当

  • 函数体复杂
  • 需要多行
  • 需要命名(递归)
  • 需要多参数列表
clojure
(map (fn [x]
       (let [y (* x x)]
         (if (> y 10)
           y
           0)))
     [1 2 3 4 5])

7.2 限制对比 #

特性 fn #()
多行表达式 支持 不支持
嵌套函数 支持 不支持
多参数列表 支持 不支持
命名递归 支持 不支持
简洁性 较长 简洁

八、实践示例 #

8.1 延迟计算 #

clojure
(defn lazy-computation [f]
  (fn []
    (f)))

(def expensive (lazy-computation
                 #(reduce + (range 1000000))))

(expensive)

8.2 函数工厂 #

clojure
(defn make-validator [pred message]
  (fn [value]
    (when-not (pred value)
      message)))

(def positive? (make-validator pos? "Must be positive"))
(def non-empty? (make-validator (complement empty?) "Must not be empty"))

(positive? 5)

(positive? -1)

8.3 事件处理 #

clojure
(defn make-handler [action]
  (fn [event]
    (action (:data event))))

(def handle-save (make-handler #(println "Saving:" %)))
(def handle-delete (make-handler #(println "Deleting:" %)))

(handle-save {:data {:id 1 :name "Item"}})

8.4 柯里化 #

clojure
(defn curry [f]
  (fn [& args]
    (if (>= (count args) 2)
      (apply f args)
      (fn [x]
        (apply f (conj args x))))))

(def curried-add (curry +))

((curried-add 1) 2)

(curried-add 1 2)

九、常见陷阱 #

9.1 #()中的嵌套函数 #

clojure
(#(fn [y] (+ % y)) 5)

(#((fn [y] (+ % y))) 5)

9.2 % vs %1 #

clojure
(#(vector % %1) 1)

(#(vector % %2) 1 2)

9.3 闭包中的变量 #

clojure
(def functions
  (for [i (range 5)]
    (fn [] i)))

(map (fn [f] (f)) functions)

十、总结 #

匿名函数语法:

语法 用途
(fn [x] body) 完整形式
#(* % %) 简洁形式
#(+ %1 %2) 多参数

关键点:

  1. fn 是完整形式,#() 是语法糖
  2. 闭包捕获定义时的环境
  3. 词法作用域决定变量绑定
  4. 选择合适的形式提高可读性

下一步,让我们学习多态与多重方法!

最后更新:2026-03-27