Clojure教程:函数

来源:互联网 发布:遭遇网络暴力怎么办 编辑:程序博客网 时间:2024/05/22 13:18

本文包括如下内容: 

  • 如何定义函数 
  • 如何执行函数 
  • 多元数函数(Multi-arity Functions) 
  • 不定参函数(Variadic Functions) 
  • 高阶函数 
  • 其它函数相关内容 

版权: 
This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images & stylesheets). The source is available on Github. 

目录

  • 1 针对Clojure版本
  • 2 简介
  • 3 如何定义函数
  • 4 匿名函数
  • 5 如何执行函数
  • 6 多元数函数
  • 7 解构函数参数
    • 7.1 基于位置的解构
    • 7.2 解构Map
  • 8 变参函数
    • 8.1 具名参数(Named Parameters)
  • 9 高阶函数
  • 10 私有函数
  • 11 关键字作为函数
  • 12 Map作为函数
  • 13 Set作为函数
  • 14 函数作为比较器
  • 15 结束语
  • 16 贡献

针对Clojure版本

简介

Clojure是函数式编程语言.自然的,函数是Clojure非常重要的一部分. 

如何定义函数

函数定义一般使用defn宏: 

(defn round  [d precision]  (let [factor (Math/pow 10 precision)]    (/ (Math/floor (* d factor)) factor)))

类型提示有时能避免编译器使用反射,从而能生成更高效的字节码.但是,基本上 
你没必要使用类型提示.后期优化时再考虑. 
函数可以添加注释文档,给API添加文档说明是个好习惯: 

(defn round  "Round down a double to the given precision (number of significant digits)"  [d precision]  (let [factor (Math/pow 10 precision)]    (/ (Math/floor (* d factor)) factor)))

在Clojure中函数参数可以有类型提示,不过是可选的. 

(defn round  [^double d ^long precision]  (let [factor (Math/pow 10 precision)]    (/ (Math/floor (* d factor)) factor)))

函数还可以定义前置和后置条件来限制函数的参数和返回值. 

(defn round  "Round down a double to the given precision (number of significant digits)"  [^double d ^long precision]  {:pre [(not-nil? d) (not-nil? precision)]}  (let [factor (Math/pow 10 precision)]    (/ (Math/floor (* d factor)) factor)))

在上面的例子中,我没使用了前置条件来检查两个参数是否为nil.not-nil?宏(或 
函数),没有在该例子中展示,我们假设它已经在其它地方实现了. 

匿名函数

匿名函数使用fn特殊形式来定义; 

(fn [x]  (* 2 x))

匿名函数可以赋给局部变量,作为参数传递给函数或作为函数的返回值. 

(let [f (fn [x]          (* 2 x))]  (map f (range 0 10)))

Clojure提供了语法糖来简化匿名函数的编写: 

(let [f #(* 2 %)]  (map f (range 0 10)))

%表示第一个参数.如果要引用多个参数,可以使用%1,%2.以此类推: 

;; 一个包含了三个参数的匿名函数,返回三个参数的和(let [f #(+ %1 %2 %3)]  (f 1 2 3))

语法糖简化了代码,但是降低了代码的可读性.所以使用前请斟酌. 

如何执行函数

要执行函数,只需要将函数名放在list的第一个位置就行了: 

(format "Hello, %s" "world")

对于赋给局部变量,变量或这从参数传递的函数,此法同样适用: 

(let [f format]  (f "Hello, %s" "world"))

另外你也可以使用clojure.core/apply来执行函数 

(apply format "Hello, %s" ["world"])(apply format "Hello, %s %s" ["Clojure" "world"])

clojure.core/apply一般在调用不定参函数或者需要将参数作为集合传递时才会 
使用 

多元数函数

在Clojure中有多元数函数: 

(defn tax-amount  ([amount]     (tax-amount amount 35))  ([amount rate]     (Math/round (double (* amount (/ rate 100))))))

在上面的例子中,只有一个参数的函数调用了有两个参数的函数.这在多参函数中 
很常见(相当于默认值的功能).Clojure没有提供默认值的功能,是因为JVM不支持. 
在Clojure中,元数只和参数个数有关,而和参数类型无关.这是因为Clojure是动 
态语言,类型信息可能在编译期是无效的. 

(defn range  ([]    (range 0 Double/POSITIVE_INFINITY 1))  ([end]    (range 0 end 1))  ([start end]    (range start end 1))  ([start end step]    (comment Omitted for clarity)))

解构函数参数

有时函数的参数是数据结构:向量,序列,map.当你想访问这些数据结构的其中一 
部分数据时,你可能需要编写类似下面的代码 

(defn currency-of  [m]  (let [currency (get m :currency)]    currency))

对向量来说,需要编写类似这样的代码: 

(defn currency-of  [pair]  (let [amount   (first  pair)        currency (second pair)]    currency))

但是呢,这样的样板代码可重用性并不高.所以Clojure提供了解构. 

基于位置的解构

解构向量的方式如下:使用一个向量替换原来作为函数参数的数据结构,这个向量包含了占位符, 
而占位符会将对应位置的数据结构的值绑定过来.举例来说,如果一个参数是一对 
值,你想要获得第二个参数值,那么代码可以这样写: 

(defn currency-of  [[amount currency]]  currency)

在上面的例子中,参数的第一个值被绑定到了amount上,第二个参数是被绑定到了 
currency上.看起来很棒,但是,这里我们并没有使用amount.在这种情况下,我们 
可以使用下划线来忽略它: 

(defn currency-of  [[_ currency]]  currency)
(defn first-first  [[[i _] _]]  i)

虽然本文不会涉及到let这个form和本地变量.但是需要提一下,解构对let也生效,而 
且作用一模一样 

(let [pair         [10 :gbp]      [_ currency] pair]  currency)

解构Map

对Map和Record的解构方式与解构向量略有不同: 

(defn currency-of  [{currency :currency}]  currency)

在上面的例子中,我们想把:currency这个key对应的value绑定到currency上.Key 
并不一定需要是关键字: 

(defn currency-of  [{currency "currency"}]  currency)(defn currency-of  [{currency 'currency}]  currency)

我们可以一次性解构多个key: 

(defn currency-of  [{:keys [currency amount]}]  currency)

上面的例子中,keys需要为关键字,其名字与currency和amount相同 
(即:currency,:amount).如果keys是字符串,则将上面的:keys改为:strs即可: 

(defn currency-of  [{:strs [currency amount]}]  currency)
(defn currency-of  [{:syms [currency amount]}]  currency)

当然了,使用关键字作为key在Clojure中是推荐做法. 
解构Map时,如果找不到我们需要的key的值,我们可以设置默认值: 

(defn currency-of  [{:keys [currency amount] :or {currency :gbp}}]  currency)

此功能对于编写包含额外属性的函数大有裨益. 
和基于位置的解构相同,Map解构对let同样适用: 

(let [money               {:currency :gbp :amount 10}     {currency :currency} money]  currency)

变参函数

参数数量可变的函数叫做变参函数.clojure.core/str和clojure.core/format就 
是两个变参函数: 

(str "a" "b")      ; "ab"(str "a" "b" "c")  ; "abc"(format "Hello, %s" "world")               ; "Hello, world"(format "Hello, %s %s" "Clojure" "world")  ; "Hello, Clojure world"

要定义变参函数,只需要在变参前面加个&就可以了: 

(defn log  [message & args]  (comment ...))

在上面的例子中,只有一个参数是必须的.变参函数的调用方式和普通函数相同: 

(defn log  [message & args]  (println "args: " args))(log "message from " "192.0.0.76")
user=> (log "message from " "192.0.0.76")args:  (192.0.0.76)user=> (log "message from " "192.0.0.76" "service:xyz")args:  (192.0.0.76 service:xyz)

你可以看到,可选的参数被包装到了一个list里面. 

具名参数(Named Parameters)

具名参数是通过对变参函数的解构来实现的. 
从解构变参函数的立场上来看,具名参数具有较好的可读性.下面是一个例子: 

(defn job-info  [& {:keys [name job income] :or {job "unemployed" income "$0.00"}}]  (if name    [name job income]    (println "No name specified")))
user=> (job-info :name "Robert" :job "Engineer")["Robert" "Engineer" "$0.00"]user=> (job-info :job "Engineer")No name specified

如果不使用变参列表,那么你需要使用形如{:name “Robert” :job “Engineer”}这 
样的map作为参数. 
关键字的默认值依据:as后跟的map来确定.如果关键字没有传递值,且无默认值, 
则为nil. 

高阶函数

高阶函数是将其它函数作为参数的函数.高阶函数在函数式编程中是很重要的技 
术,在Clojure中经常使用到.一个高阶函数的例子是将一个函数和一个集合作为 
参数,返回符合这个函数条件的集合.在Clojure中,这叫做clojure.core/filter: 

(filter even? (range 0 10))  ;=>(0 2 4 6 8)

上面的例子中,clojure.core/filter函数接收clojure.core/even?作为参数. 
clojure.core中有很多高阶函数.经常使用的函数请参见 clojure.core 

私有函数

在Clojure中,函数可以在其命名空间中设置为私有的. 
具体细节请参考 这里 

关键字作为函数

在Clojure中,关键字可以作为函数使用.他们接收map或record并从中查找信息: 

(:age {:age 27 :name "Michael"})  ; 27

他们经常和高阶函数结合使用: 

(map :age [{:age 45 :name "Joe"} {:age 42 :name "Jill"} {:age 17 :name "Matt"}])  ; ⇒ (45 42 17)
(-> [{:age 45 :name "Joe"} {:age 42 :name "Jill"}] first :name)  ; "Joe"

Map作为函数

Clojure的Map也能作为函数使用,来查找key对应的value: 

({:age 42 :name "Joe"} :name)     ; "Joe"({:age 42 :name "Joe"} :age)      ; 42({:age 42 :name "Joe"} :unknown)  ; nil

需要注意的是,虽然大部分情况下map和record可以等同对待,但是这里不 
行!record不能作为函数使用! 

Set作为函数

(#{1 2 3} 1)   ; ⇒ 1(#{1 2 3} 10)  ; nil(#{:us :au :ru :uk} :uk)  ; :uk(#{:us :au :ru :uk} :cn)  ; nil

此功能被用来验证某值是否set中: 

(when (countries :in)  (comment ...))(if (countries :in)  (comment Implement positive case)  (comment Implement negative case))

因为在Clojure中除了false和nil,其它值都是true. 

函数作为比较器

Clojure函数实现了java.util.Comparator接口,所以能作为比较器使用. 

结束语

函数是Clojure的核心.他们通过defn宏来定义,可以有多个元数,不定参数并支持 
参数解构.函数参数和返回值可以有类型提示,当然这不是必须的. 
函数是一等值,它能被传递给其它函数.这是函数式编程的基石. 
有一些数据类型有函数特性.适时的使用这些特性可以是代码更简洁易读. 

贡献

Michael Klishin michael@defprotocol.org, 2012 (original author) 
Translated by Ivan 2014 

0 0
原创粉丝点击