创建函数
Clojure 作为一门函数式语言,函数是头等公民。我们可以通过调用 defn
来命名一个函数:
;; 名称 参数 函数体
;; ----- ------ -------------------
(defn greet [name] (str "Hello, " name) )
这样定义出来的函数有一个参数 name
,然后我们就可以调用这个函数:
user=> (greet "students")
"Hello, students"
Multi-arity 函数
同一个函数可以定义几个不同数量的参数(不同的 "arity"),不同的 arity 都定义在同一个 defn
内,每一个 arity 都有一个不同的函数体,并且不同的 arity 之间可以相互调用:
(defn messenger
([] (messenger "Hello world!"))
([msg] (println msg)))
这个函数声明了两个 arity,分别是没有参数的和有 1 个参数的,当你调用的时候函数会自动匹配合适数量参数的 arity:
user=> (messenger)
Hello world!
nil
user=> (messenger "Hello class!")
Hello class!
nil
可变参数(Variadic)函数
一个函数的参数数量可能是不固定的,这个时候我们可以定义一个可变参数的函数,可变参数必须在所有参数的最后,并且可以一个也没有,我们通过 &
来标记可变参数:
(defn hello [greeting & who]
(println greeting who))
这个时候 who
就成了一个可变参数,其参数数量是任意的,而 who
就是可变参数的列表:
user=> (hello "Hello" "world" "class")
Hello (world class)
我们可以看到实际上 println
输出的who
是一个列表。
匿名函数
匿名函数可以用 fn
来创建:
;; 参数 函数体
;; --------- -----------------
(fn [message] (println message) )
由于匿名函数没有函数名,所以不能被后文直接引用。匿名函数通常是在它传递给另一个函数的地方创建的。偶尔也会在创建之后立刻调用:
;; 操作符 (函数) 参数
;; -------------------------------- --------------
( (fn [message] (println message)) "Hello world!" )
;; Hello world!
这个时候匿名函数位于列表的 function position 然后被立刻调用。
在 Clojure 中只有表达式(expression)而没有声明(statements),也就是说 Clojure 中所有的语句都是会被求值的,甚至是 if
。上面 fn
调用之后就返回一个函数。
def 和 fn 的比较
事实上 defn
可以看作是 def
和 fn
一起的缩写形式,下面两条语句是等价的:
(defn greet [name] (str "Hello, " name))
(def greet (fn [name] (str "Hello, " name)))
匿名函数句法
除了用 fn
创建匿名函数,还可以用 #()
来创建匿名函数,这种方式忽略参数表和参数的名称而只管它们的位置:
%
用来表示单个参数%1
,%2
,%3
, 等用来表示多个参数%&
用来表示可变参数
嵌套匿名函数会导致参数产生歧义,所以这种句法不允许嵌套。
;; 等价于: (fn [x] (+ 6 x))
#(+ 6 %)
;; 等价于: (fn [x y] (+ x y))
#(+ %1 %2)
;; 等价于: (fn [x y & zs] (println x y zs))
#(println %1 %2 %&)
如果你这么写:
;; 不要这么写
#([%])
这个匿名函数展开之后等价于:
(fn [x] ([x]))
这样会把一个参数转成一个向量(vector)然后直接调用。你可以用下面的写法:
;; 用这种方法代替:
#(vector %)
;; 或者这样:
(fn [x] [x])
;; 或者直接简单的调用函数本身:
vector
应用函数
apply
apply
函数可以对参数和最后一个序列(sequence)调用同一个函数求值,并且最后一个参数必须是序列。
(apply f '(1 2 3 4)) ;; 等价于 (f 1 2 3 4)
(apply f 1 '(2 3 4)) ;; 等价于 (f 1 2 3 4)
(apply f 1 2 '(3 4)) ;; 等价于 (f 1 2 3 4)
(apply f 1 2 3 '(4)) ;; 等价于 (f 1 2 3 4)
上面四种形式都等价于 (f 1 2 3 4)
。apply
在你需要对有序列的参数进行处理的时候会非常有用,比如你可以用 apply
避免这种写法:
(defn plot [shape coords] ;; coords is [x y]
(plotxy shape (first coords) (second coords)))
取而代之的是这种简单的写法:
(defn plot [shape coords]
(apply plotxy shape coords))
变量和闭包
let
let
可以把一个符号和一个值在其作用域(lexical scope)内进行关联。
;; 产生关联 这些名字可以在这里使用
;; ------------ ----------------------
(let [name value] (code that uses name))
let
可以定义 0 个或多个表达式:
(let [x 1
y 2]
(+ x y))
这里分别对 x
和 y
与数值 1 和 2 进行关联,然后对它们求和。
(defn messenger [msg]
(let [a 7
b 5
c (clojure.string/capitalize msg)]
(println a b c)
) ;; let 语法作用域结束
) ;; 函数语法作用域结束
上面的 messenger 函数为有一个 msg
参数,msg
在函数语法作用域内有效,而 let 关联的三个变量 a
, b
, c
则只在 let
函数作用域内有效。
闭包
fn
可以创建一个"闭包"。
(defn messenger-builder [greeting]
(fn [who] (println greeting who))) ; closes over greeting
;; greeting provided here, then goes out of scope
(def hello-er (messenger-builder "Hello"))
;; greeting value still available because hello-er is a closure
(hello-er "world!")
;; Hello world!