前言

?作為一門函數(shù)式編程語(yǔ)言,深入了解函數(shù)的定義和使用自然是十分重要的事情,下面我們一起來(lái)學(xué)習(xí)吧!

3種基礎(chǔ)定義方法

defn

定義語(yǔ)法

(defn name [params*]
  exprs*)

示例

(defn tap [ns x]
  (println ns x)
    x)

fn

定義語(yǔ)法

(fn name? [params*]
  exprs*)

示例

(def tap
  (fn [ns x]
    (println ns x)
    x))

其實(shí)defn是個(gè)macro,最終會(huì)展開(kāi)為fn這種定義方式。因此后面的均以fn這種形式作說(shuō)明。

Lambda表達(dá)式

定義語(yǔ)法

#(expr)

示例

(def tap
  #(do
     (println %1 %2)
     %2))

注意:

  1. Lambda表達(dá)式的函數(shù)體只允許使用一個(gè)表達(dá)式,因此要通過(guò)special formdo來(lái)運(yùn)行多個(gè)表達(dá)式;

  2. 入?yún)ymbol為%1,%2,...%n,當(dāng)有且只有一個(gè)入?yún)r(shí)可以使用%來(lái)指向該入?yún)ⅰ?/p>

Metadata——為函數(shù)附加元數(shù)據(jù)

?Symbol和集合均支持附加metadata,以便向編譯器提供額外信息(如類型提示等),而我們也可以通過(guò)metadata來(lái)標(biāo)記源碼、訪問(wèn)策略等信息。
?對(duì)于命名函數(shù)我們自然要賦予它Symbol,自然就可以附加元數(shù)據(jù)了。
?其中附加:privatedefn-定義函數(shù)目的是一樣的,就是將函數(shù)的訪問(wèn)控制設(shè)置為private(默認(rèn)為public),但可惜的是cljs現(xiàn)在還不支持:private,所以還是要用名稱來(lái)區(qū)分訪問(wèn)控制策略。
示例:

;; 定義
(defn
 ^{:doc "my sum function"
   :test (fn []
             (assert (= 12 (mysum 10 1 1))))   :custom/metadata "have nice time!"}
  mysum [& xs]
        (apply + xs))

;; 獲取Var的metadata
(meta #'mysum);;=>
;; {:name mysum
;;  :custom/metadata "have nice time!";;  :doc "my sum function";;  :arglists ([& xs])
;;  :file "test";;  :line 126;;  :ns #<Namespace user>;;  :test #<user$fn_289 user$fn_289@20f443>}

若只打算設(shè)置document string而已,那么可以簡(jiǎn)寫為

(defn mysum  "my sum function"
  [& xs]
  (apply + xs))

雖然cljs只支持:doc

根據(jù)入?yún)?shù)目實(shí)現(xiàn)函數(shù)重載(Multi-arity Functions)

示例

(fn tap
  ([ns] (tap ns nil))
  ([ns x] (println ns x))
  ([ns x & more] (println ns x more)))

參數(shù)解構(gòu)

?cljs為我們提供強(qiáng)大無(wú)比的入?yún)⒔鈽?gòu)能力,也就是通過(guò)聲明方式萃取入?yún)?/p>

基于位置的解構(gòu)(Positional Destructuring)

;; 定義1(def currency-of
  (fn [[amount currency]]
    (println amount currency)
    amount));; 使用1(currency-of [12 "US"]);; 定義2(def currency-of
  (fn [[amount currency [region ratio]]]
    (println amount currency region ratio)
    amount));; 使用2(currency-of [12 "US" ["CHINA" 6.7]])

鍵值對(duì)的解構(gòu)(Map Destructuring)

;; 定義1,鍵類型為Keyword(def currency-of
  (fn [[currency :curr]]
    (println currency)));; 使用1(currency-of {:curr "US"});; 定義2,鍵類型為String(def currency-of
  (fn [[currency "curr"]]
    (println currency)));; 使用2(currency-of {"curr" "US"});; 定義3,鍵類型為Symbol(def currency-of
  (fn [[currency 'curr]]
    (println currency)));; 使用3(currency-of {'curr "US"});; 定義4,一次指定多個(gè)鍵(def currency-of
  (fn [{:keys [currency amount]}]
    (println currency amount)));; 使用4(currency-of {:currency "US", :amount 12});; 定義5,一次指定多個(gè)鍵(def currency-of
  (fn [{:strs [currency amount]}]
    (println currency amount)));; 使用5(currency-of {"currency" "US", "amount" 12});; 定義6,一次指定多個(gè)鍵(def currency-of
  (fn [{:syms [currency amount]}]
    (println currency amount)));; 使用6(currency-of {'currency "US", 'amount 12});; 定義7,默認(rèn)值(def currency-of
  (fn [{:keys [currency amount] :or {currency "CHINA"}}]
    (println currency amount)));; 使用7(currency-of {:amount 100}) ;;=> 100CHINA;; 定義8,命名鍵值對(duì)(def currency-of
  (fn [{:keys [currency amount] :as orig}]
    (println (:currency orig))))

(currency-of {'currency "US", 'amount 12}) ;;=> US

可變?nèi)雲(yún)?Variadic Functions)

通過(guò)&定義可變?nèi)雲(yún)?,可變?nèi)雲(yún)H能作為最后一個(gè)入?yún)?lái)使用

(def tap
  (fn [ns & more]
    (println ns (first more))))

(tap "user.core" "1" "2" "3") ;;=> user.core1

命名入?yún)?Named Parameters/Extra Arguments)

?通過(guò)組合可變?nèi)雲(yún)⒑蛥?shù)解構(gòu),我們可以得到命名入?yún)?/p>

(def tap
  (fn [& {:keys [ns msg] :or {msg "/nothing"}}]
    (println ns msg)))

(tap :ns "user.core" :msg "/ok") ;;=> user.core/ok(tap :ns "user.core") ;;=> user.core/nothing

Multimethods

?Multi-Arity函數(shù)中我們可以通過(guò)入?yún)?shù)目來(lái)調(diào)用不同的函數(shù)實(shí)現(xiàn),但有沒(méi)有一種如C#、Java那樣根據(jù)入?yún)㈩愋蛠?lái)調(diào)用不同的函數(shù)實(shí)現(xiàn)呢?clj/cljs為我們提供Multimethods這一殺技——不但可以根據(jù)類型調(diào)用不同的函數(shù)實(shí)現(xiàn),還可以根據(jù)以下內(nèi)容呢!

  1. 類型

  2. 屬性

  3. 元數(shù)據(jù)

  4. 入?yún)㈤g關(guān)系

?想說(shuō)"Talk is cheap, show me the code"嗎?在看代碼前,我們先看看到底Multimethods的組成吧
1.dispatching function
?用于對(duì)函數(shù)入?yún)⒆鞑僮?,如獲取類型、值、運(yùn)算入?yún)㈥P(guān)系等,然后將返回值作為dispatching value,然后根據(jù)dispatching value調(diào)用具體的函數(shù)實(shí)現(xiàn)。

;; 定義dispatching function(defmulti name docstring? attr-map? dispatch-fn & options);; 其中options是鍵值對(duì)
;; :default :default,指定默認(rèn)dispatch value的值,默認(rèn)為:default;; :hierarchy {},指定使用的hierarchy object

2.method
?具體函數(shù)實(shí)現(xiàn)

;; 定義和注冊(cè)新的函數(shù)到multimethod(defmethod multifn dispatch-val & fn-tail)

3.hierarchy object
?存儲(chǔ)層級(jí)關(guān)系的對(duì)象,默認(rèn)情況下所有相關(guān)的Macro和函數(shù)均采用全局hierarchy object,若要采用私有則需要通過(guò)(make-hierarchy)來(lái)創(chuàng)建。

還是一頭霧水?上示例吧!
示例1 —— 根據(jù)第二個(gè)入?yún)⒌膶蛹?jí)關(guān)系

(defmulti area
  (fn [x y]
    y))

(defmethod area ::a
  [x y] (println "derive from ::a"))
(defmethod area :default
  [x y] (println "executed :default"))

(area 1 `a) ;;=> executed :default(derive `a :a)
(area 1 `a) ;;=>derive from ::a

示例2 -- 根據(jù)第一個(gè)入?yún)⒌闹?/p>

(defmulti area
  (fn [x y]
    x))

(defmethod area 1
  [x y] (println "x is 1"))
(defmethod area :default
  [x y] (println "executed :default"))

(area 2 `a) ;;=> executed :default(area 1 :b) ;;=> x is 1

示例3 -- 根據(jù)兩入?yún)?shù)值比較的大小

(defmulti area
  (fn [x y]
    (> x y)))

(defmethod area true
  [x y] (println "x > y"))
(defmethod area :default
  [x y] (println "executed :default"))

(area 1 2) ;;=> executed :default(area 2 3) ;;=> x > y

?刪除method

;; 函數(shù)簽名(remove-method multifn dispatch-val);; 示例(remove-method area true)

分發(fā)規(guī)則

?先對(duì)dispatching value和method的dispatching-value進(jìn)行=的等于操作,若不匹配則對(duì)兩者進(jìn)行isa?的層級(jí)關(guān)系判斷操作,就這樣遍歷所有注冊(cè)到該multimethod的method,得到一組符合的method。若這組method的元素個(gè)數(shù)有且僅有一個(gè),則執(zhí)行該method;若沒(méi)有則執(zhí)行:default method,若還是沒(méi)有則拋異常。若這組method的元素個(gè)數(shù)大于1,且沒(méi)有人工設(shè)置優(yōu)先級(jí),則拋異常。
?通過(guò)prefer-method我們可以設(shè)置method的優(yōu)先級(jí)

(derive `a `b)
(derive `c `a)

(defmulti test
  (fn [x] (x)))

(defmethod test `a
  [x] (println "`a"))

(defmethod test `b
  [x] (println "`b"));; (test `c) 這里就不會(huì)出現(xiàn)多個(gè)匹配的method(prefer-method `a `b)
(test `c) ;;=> `a

層級(jí)關(guān)系

?層級(jí)關(guān)系相關(guān)的函數(shù)如下:

;; 判斷層級(jí)關(guān)系(isa? h? child parent);; 構(gòu)造層級(jí)關(guān)系(derive h? child parent);; 解除層級(jí)關(guān)系(underive h? child parent);; 構(gòu)造局部hierarchy object(make-hierarchy)

上述函數(shù)當(dāng)省略h?時(shí),則操作的層級(jí)關(guān)系存儲(chǔ)在全局的hierarchy object中。
注意:層級(jí)關(guān)系存儲(chǔ)在全局的hierarchy object中時(shí),Symbole、Keyword均要包含命名空間部分(即使這個(gè)命名空間并不存在),否則會(huì)拒絕。

(ns cljs.user);; Symbole, `b會(huì)展開(kāi)為cljs.user/b(derive 'dummy/a `b);; Keyword, ::a會(huì)展開(kāi)為cljs.user/:a(derive ::a ::b)

另外還有parentancestorsdescendants

(derive `c `p)
(derive `p `pp);; 獲取父層級(jí)(parent `c) ;;=> `p;; 獲取祖先(ancestors `c) ;;=> #{`p `pp};; 獲取子孫(descendants `pp) ;;=> #{`p `c}

局部層級(jí)關(guān)系

?通過(guò)(make-hierarchy)可以創(chuàng)建一個(gè)用于實(shí)現(xiàn)局部層級(jí)關(guān)系的hierarchy object

(def h (make-hierarchy))
(def h (derive h 'a 'b))
(def h (derive h :a :b))

(isa? h 'a 'b)
(isa? h :a :b)

注意:局部層級(jí)關(guān)系中的Symbol和Keyword是可以包含也可以不包含命名空間部分的哦!

Condition Map

?對(duì)于動(dòng)態(tài)類型語(yǔ)言而言,當(dāng)入?yún)⒉环虾瘮?shù)定義所期待時(shí),是將入?yún)⒏袷交癁榉掀诖?,還是直接報(bào)錯(cuò)呢?我想這是每個(gè)JS的工程師必定面對(duì)過(guò)的問(wèn)題。面對(duì)這個(gè)問(wèn)題我們應(yīng)該分階段分模塊來(lái)處理。

  1. 開(kāi)發(fā)階段,對(duì)于內(nèi)核模塊,讓問(wèn)題盡早暴露;

  2. 生產(chǎn)階段,對(duì)于與用戶交互的模塊,應(yīng)格式化輸入,并在后臺(tái)記錄跟蹤問(wèn)題。
    ?而clj/cljs函數(shù)中的condition map就是為我們?cè)陂_(kāi)發(fā)階段提供對(duì)函數(shù)入?yún)?、函?shù)返回值合法性的斷言能力,讓我們盡早發(fā)現(xiàn)問(wèn)題。

(fn name [params*] condition-map? exprs*)
(fn name ([params*] condition-map? exprs*)+); condition-map? => {:pre [pre-exprs*];                    :post [post-exprs*]}; pre-exprs 就是作為一組對(duì)入?yún)⒌臄嘌? post-exprs 就是作為一組對(duì)返回值的斷言

示例

(def mysum
  (fn [x y]
      {:pre  [(pos? x) (neg? y)]       :post [(not (neg? %))]}
      (+ x y)))

(mysum 1 1)  ;; AssertionError Assert failed: (neg? y)  user/mysum(mysum -1 1) ;; AssertionError Assert failed: (pos? x)  user/mysum(mysum 1 -2) ;; AssertionError Assert failed: not (neg? %))  user/mysum

?在pre-exprs中我們可以直接指向函數(shù)的入?yún)?,在post-exprs中則通過(guò)%來(lái)指向函數(shù)的返回值。
?雖然增加函數(shù)執(zhí)行的前提條件,而且可以針對(duì)函數(shù)的值、關(guān)系、元數(shù)據(jù)等進(jìn)行合法性驗(yàn)證,但依舊需要在運(yùn)行時(shí)才能觸發(fā)驗(yàn)證(這些不是運(yùn)行時(shí)才觸發(fā)還能什么時(shí)候能觸發(fā)呢?)。對(duì)動(dòng)態(tài)類型語(yǔ)言天然編譯期數(shù)據(jù)類型驗(yàn)證,我們可以通過(guò)core.typed這個(gè)項(xiàng)目去增強(qiáng)哦!

總結(jié)

?現(xiàn)在我們可以安心把玩函數(shù)了,oh yeah!
尊重原創(chuàng),轉(zhuǎn)載請(qǐng)注明來(lái)自:http://www.cnblogs.com/fsjohnhuang/p/7137597.html ^_^肥仔John

http://www.cnblogs.com/fsjohnhuang/p/7137597.html