前几章中,我们用了一种比较迂回的方式创建函数:把匿名函数绑定到一个变量上。实际上,clojure提供了一个更好的方式做同一件事情。“defn” 这个函数就是专门用于定义函数的。
在我们使用defn之前,我们再回顾一下之前我们怎么使用def来创建函数的,然后我们使用defn来做同一件事情对比一下。
;;使用def
=>(def alphabet-length (fn [ ](count alphabet)))
#'user/alphabet-length
=>(alphabet-length)
26
;;使用defn
=>(defn alphabet-length [ ](count alphabet))
#'user/alphabet-length
=>(alphabet-length)
26
上面两种方式做的都是同一件事情。但是defn能做更多的事情。下面是defn定义函数的一个脚手架:
[1] (defn name 函数名
[2] "description" 函数描述 (可选)
[3] {metadata} 元数据 (可选)
[4] [arguments] 参数列表
[5] body-of-expressions...) 函数体
上面我们可以看出,defn在定义函数时可以提供更多的信息。
下面让我们用上面这些信息定义一个函数:
=>(defn select-random
"从一个列表中随机返回一个元素"
{:added "1.2"} ;; 元数据
[options]
(nth options (rand-int (count options))))
#'user/select-random
(count options) 用于计算options包含的元素数量。(nth options x) 用于从options中获取第x个元素(从0开始,类似java中的list的get方法)
我们之前说过clojure是lisp的一种方言。lisp 是 “List Processor”的缩写,就是列表解析的意思,使用列表来表示所有的东西(S表达式)。从我们写的代码也可以看出,整个代码结构就是一个嵌套的列表。现在让我们用列表结构来保存数据:
=>(list "growl" "lick" "jump")
("growl" "lick" "jump")
我们之前定义的函数select-random需要的参数正是一个列表,正好我们就可以用来测试:
=>(select-random (list "growl" "lick" "jump"))
"jump"
=>(select-random (list "growl" "lick" "jump"))
"growl"
运行一切正常,说明select-random没什么问题。我们可以在一个新的函数中来使用它。我们来创建一个用于问候的函数greeting。
=>(defn greeting
"Composes a greeting sentence. Expects both the name of a greeter
and the name of whom is to be greeted for arguments. An approach
and an action are randomly selected."
{:added "1.2"}
[greeter whom]
;;str 用于组装字符串
(str greeter " greeted " whom " with a "
(select-random (list "ferocious" "wimpy" "precarious" "subtle")) " "
(select-random (list "growl" "lick" "jump")) "!"))
#'user/greeting
=>(greeting "Jon" "Thaddeus")
"Jon greeted Thaddeus with a wimpy growl!"
=>(greeting "Jon" "Thaddeus")
"Jon greeted Thaddeus with a precarious lick!"
当然,上面的问候函数不是很完美。我们可以把问候语句单独提出来。
=>(def approaches (list "ferocious" "wimpy" "precarious" "subtle"))
'user/approaches
=>(def actions (list "growl" "lick" "jump"))
#'user/actions
然后在greeting中使用绑定的列表:
=>(defn greeting
"Composes a greeting sentence. Expects both the name of a greeter
and the name of whom is to be greeted for arguments. An approach
and an action are randomly selected."
{:added "1.2"}
[greeter whom]
(str greeter " greeted " whom " with a "
(select-random approaches) " "
(select-random actions) "!"))
#'user/greeting
现在可读性好多了吧,把变化的部分单独抽象出来这个原则对于函数式编程也是通用的哦。这样我们就可以在不修改函数的情况下改变问候语句了。
至于函数定义中的元数据有什么作用,暂时保密,后面会单独来讲。