Clojure编程基础:读者宏、函数、变量与命名空间
立即解锁
发布时间: 2025-08-19 02:29:21 阅读量: 1 订阅数: 4 


探索Clojure编程:从入门到精通
### Clojure编程基础:读者宏、函数、变量与命名空间
#### 1. 读者宏(Reader Macros)
Clojure的读者(reader)负责将文本转换为Clojure数据结构。除了基本形式外,Clojure读者还识别一组读者宏。读者宏是由前缀宏字符触发的特殊读者行为。
最常见的读者宏是注释。触发注释的宏字符是分号(;),特殊的读者行为是“忽略本行剩余的所有内容”。
读者宏是较长列表形式的缩写,用于减少代码的杂乱。例如,引号字符(')可防止求值:
```clojure
'(1 2)
-> (1 2)
```
`'(1 2)` 等价于更长的 `(quote (1 2))`:
```clojure
(quote (1 2))
-> (1 2)
```
以下是一些读者宏的概述:
| 读者宏 | 示例 | 主要覆盖内容 |
| ---- | ---- | ---- |
| 匿名函数 | #(.toUpperCase %) | 函数部分 |
| 注释 | ; 单行注释 | 读者宏部分 |
| 解引用 | @form => (deref form) | 状态章节 |
| 元数据 | ^metadata form | 元数据部分 |
| 引号 | ’form => (quote form) | 形式部分 |
| 正则表达式模式 | #"foo" => java.util.regex.Pattern | - |
| 语法引号 | ‘x | 简化宏部分 |
| 取消引用 | ~ | 简化宏部分 |
| 取消引用拼接 | ~@ | 简化宏部分 |
| 变量引号 | #’x => (var x) | 状态章节 |
Clojure不允许程序定义新的读者宏。这样做的理由在Clojure邮件列表中已有解释和讨论。虽然对于有Lisp背景的人来说可能会感到沮丧,但这种灵活性上的妥协使Clojure拥有更稳定的核心。自定义读者宏可能会使Clojure程序的互操作性降低,且更难阅读。
#### 2. 函数(Functions)
在Clojure中,函数调用只是一个列表,其第一个元素解析为一个函数。例如,调用 `str` 函数可将其参数连接成一个字符串:
```clojure
(str "hello" " " "world")
-> "hello world"
```
函数名通常使用连字符分隔,如 `clear-agent-errors`。如果一个函数是谓词,按照惯例,其名称应以问号结尾。例如:
```clojure
(string? "hello")
-> true
(keyword? :hello)
-> true
(symbol? 'hello)
-> true
```
要定义自己的函数,可使用 `defn`:
```clojure
(defn name doc-string? attr-map? [params*] body)
```
`attr-map` 用于将元数据与函数的变量关联起来,这在元数据部分会单独介绍。下面创建一个 `greeting` 函数,它接受一个名称并返回以 “Hello” 开头的问候语:
```clojure
(defn greeting
"Returns a greeting of the form 'Hello, username.'"
[username]
(str "Hello, " username))
```
可以调用 `greeting` 函数:
```clojure
(greeting "world")
-> "Hello, world"
```
也可以查看 `greeting` 函数的文档:
```clojure
user=> (doc greeting)
-------------------------
exploring/greeting
([username])
Returns a greeting of the form 'Hello, username.'
```
如果调用 `greeting` 函数时省略了 `username` 参数,会抛出 `ArityException` 异常,因为Clojure函数会强制检查参数数量。若想在调用者省略 `username` 时发出通用问候语,可以使用 `defn` 的另一种形式,它接受多个参数列表和方法体:
```clojure
(defn greeting
"Returns a greeting of the form 'Hello, username.'
Default username is 'world'."
([] (greeting "world"))
([username] (str "Hello, " username)))
```
现在,再次调用无参数的 `greeting` 函数:
```clojure
(greeting)
-> "Hello, world"
```
可以通过在参数列表中包含 `&` 来创建可变参数函数。Clojure会将 `&` 后面的名称绑定到所有剩余参数的序列。例如:
```clojure
(defn date [person-1 person-2 & chaperones]
(println person-1 "and" person-2
"went out with" (count chaperones) "chaperones."))
(date "Romeo" "Juliet" "Friar Lawrence" "Nurse")
| Romeo and Juliet went out with 2 chaperones.
```
可变参数在递归定义中非常有用。
`defn` 用于在顶层定义函数。如果想在另一个函数内部创建函数,应使用匿名函数形式。
#### 2.1 匿名函数(Anonymous Functions)
除了使用 `defn` 定义命名函数外,还可以使用 `fn` 创建匿名函数。创建匿名函数至少有三个原因:
- 函数非常简短且自解释,命名会使代码更难阅读。
- 函数仅在另一个函数内部使用,需要一个局部名称,而不是顶层绑定。
- 函数在另一个函数内部创建,用于闭包某些数据。
例如,要创建一个单词序列的索引,且不关心长度小于三个字符的单词,可以定义一个 `indexable-word?` 函数:
```clojure
(defn indexable-word? [word]
(> (count word) 2))
```
然后使用该函数从句子中提取可索引的单词:
```clojure
(require '[clojure.string :as str])
(filter indexable-word? (str/split "A fine day it is" #"\W+"))
-> ("fine" "day")
```
使用匿名函数可以在一行内完成相同的操作。最简单的匿名 `fn` 形式如下:
```clojure
(fn [params*] body)
```
可以将 `indexable-word?` 的实现直接插入 `filter` 调用中:
```clojure
(filter (fn [w] (> (count w) 2)) (str/split "A fine day" #"\W+"))
-> ("fine" "day")
```
还有一种更短的匿名函数语法,使用隐式参数名称。参数命名为 `%1`、`%2` 等,也可以使用 `%` 表示第一个参数:
```clojure
#(body)
```
可以使用更短的匿名形式重写 `filter` 调用:
```clojure
(filter #(> (count %) 2) (str/split "A fine day it is" #"\W+"))
-> ("fine" "day")
```
另一个使用匿名函数的动机是在另一个函数的作用域内需要一个命名函数。例如:
```clojure
(defn indexable-words [text]
(let [indexable-word? (fn [w] (> (count w) 2))]
(filter indexable-word? (str/split text #"\W+"))))
```
可
0
0
复制全文
相关推荐










