Clojure 学习笔记 2 - Functions

创建函数

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 可以看作是 deffn 一起的缩写形式,下面两条语句是等价的:

(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))

这里分别对 xy 与数值 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!

转载于:https://www.cnblogs.com/Uwgl/p/8507879.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值