Clojure 函数定义:defn 与参数
Clojure 作为一门函数式编程语言,函数是其核心构建块。定义函数最常用的工具是 defn 宏。掌握 defn 的语法结构与参数处理方式,是编写清晰、简洁 Clojure 代码的关键。
1. 基础函数定义
使用 defn 宏 创建 一个标准的命名函数。它主要由函数名、可选的文档字符串、参数向量以及函数体组成。
- 打开 Clojure REPL(交互式解释器)。
- 输入 以下代码 定义 一个最简单的函数:
(defn say-hello []
(println "Hello, Clojure!"))
- 输入
(say-hello)调用 该函数。 - 观察 控制台输出
Hello, Clojure!。
解析 上述代码结构:
defn:用于定义函数的宏。say-hello:函数名称,符号类型。[]:参数向量,此处为空,表示无参数。(println ...):函数体,执行具体逻辑。
添加 参数以增加函数的灵活性。
- 修改 函数定义,加入 参数向量
[name]:
(defn greet [name]
(str "Hello, " name "!"))
- 输入
(greet "Developer")执行 函数。 - 查看 返回结果
"Hello, Developer!"。
2. 多元数函数
Clojure 支持函数重载,允许同一个函数名根据参数个数的不同执行不同的逻辑。这被称为“多元数定义”。
- 编写 包含两个不同参数列表的函数。使用 列表包裹多组定义:
(defn messenger
([name]
(str "Message for: " name))
([name content]
(str "To " name ": " content)))
- 输入
(messenger "Alice")测试 单参数版本。 - 输入
(messenger "Bob" "Meeting at 10am")测试 双参数版本。
理解 参数匹配流程:
graph TD
A["调用 messenger"] --> B{"判断参数数量"}
B -- "数量 = 1" --> C["执行 [name] 逻辑体"]
B -- "数量 = 2" --> D["执行 [name content] 逻辑体"]
B -- "其他数量" --> E["抛出 ArityException 错误"]
3. 可变参数处理
当函数需要接受任意数量的参数时,使用 & 符号定义可变参数。& 后面的符号会绑定剩余的所有参数,形成一个列表。
- 定义 一个接受任意数量数字的求和函数:
(defn sum-all [x & rest]
(apply + x rest))
- 输入
(sum-all 1 2 3 4 5)调用 函数。 - 分析 参数绑定情况:
x绑定第一个参数1。rest绑定剩余参数列表(2 3 4 5)。apply函数将列表展开传递给+函数进行计算。
注意:可变参数符号 & 只能出现在参数向量的最后位置。
4. 参数解构
Clojure 允许直接在参数列表中解构复杂数据结构,无需在函数体内手动提取数据。这使得代码意图更加明确。
使用 关联解构处理 Map 数据。
- 假设 传入一个包含用户信息的 Map。
- 编写 函数直接提取特定键值:
(defn print-user [{:keys [name age]}]
(println "Name:" name)
(println "Age:" age))
- 输入 以下代码 调用 函数:
(print-user {:name "Alice" :age 30 :id 101})
- 验证 输出结果:
:keys [name age]自动从 Map 中提取:name和:age对应的值。- Map 中未在解构列表中声明的键(如
:id)会被忽略。
对比 常见解构类型与应用场景:
| 解构类型 | 语法示例 | 适用场景 | 示例说明 |
|---|---|---|---|
| 顺序解构 | [a b c] |
处理列表、向量等序列数据。 | 将向量 [1 2 3] 拆分为变量 a, b, c。 |
| 关联解构 | {:keys [k1 k2]} |
处理 Map 数据。 | 从 Map 中直接提取 :k1, :k2 对应的值。 |
| 混合解构 | [{:keys [title]} author] |
处理嵌套复杂数据结构。 | 第一个元素是 Map(解构 title),第二个元素绑定给 author。 |
5. 前置条件与后置条件
Clojure 函数支持在定义时直接加入断言,用于检查输入参数和输出结果的有效性。
- 使用
:pre和:post向量 定义 条件:
(defn calculate-area [radius]
{:pre [(pos? radius)]
:post [(pos? %)]}
(* Math/PI radius radius))
- 输入
(calculate-area 5)验证 正常流程。 - 输入
(calculate-area -1)触发 前置条件失败。:pre检查radius是否为正数。:post检查返回值(%代表返回值)是否为正数。- 若条件不满足,程序抛出
AssertionError。
6. 私有函数
区分 模块内部使用的函数与对外公开的接口。
- 使用
defn-替代defn定义 私有函数:
(defn- helper-calc [x]
(* x x))
(defn public-api [x]
(+ (helper-calc x) x))
- 在 当前命名空间内 调用
(helper-calc 3),确认 执行成功。 - 尝试 在其他命名空间引用
helper-calc。- 编译器会报错,提示无法解析该符号。
- 这有助于隐藏实现细节,仅暴露必要的 API。

暂无评论,快来抢沙发吧!