javascript
(cljs/run-at (JSVM. :all) 细说函数)
前言
?作為一門函數式編程語言,深入了解函數的定義和使用自然是十分重要的事情,下面我們一起來學習吧!
3種基礎定義方法
defn
定義語法
(defn name [params*]exprs*)示例
(defn tap [ns x](println ns x)x)fn
定義語法
(fn name? [params*]exprs*)示例
(def tap(fn [ns x](println ns x)x))其實defn是個macro,最終會展開為fn這種定義方式。因此后面的均以fn這種形式作說明。
Lambda表達式
定義語法
#(expr)示例
(def tap#(do(println %1 %2)%2))注意:
Metadata——為函數附加元數據
?Symbol和集合均支持附加metadata,以便向編譯器提供額外信息(如類型提示等),而我們也可以通過metadata來標記源碼、訪問策略等信息。
?對于命名函數我們自然要賦予它Symbol,自然就可以附加元數據了。
?其中附加:private和defn-定義函數目的是一樣的,就是將函數的訪問控制設置為private(默認為public),但可惜的是cljs現在還不支持:private,所以還是要用名稱來區分訪問控制策略。
示例:
若只打算設置document string而已,那么可以簡寫為
(defn mysum"my sum function"[& xs](apply + xs))雖然cljs只支持:doc
根據入參數目實現函數重載(Multi-arity Functions)
示例
(fn tap([ns](tap ns nil))([ns x](println ns x))([ns x & more](println ns x more)))參數解構
?cljs為我們提供強大無比的入參解構能力,也就是通過聲明方式萃取入參
基于位置的解構(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]])鍵值對的解構(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,一次指定多個鍵 (def currency-of(fn [{:keys [currency amount]}](println currency amount)));; 使用4 (currency-of {:currency "US", :amount 12});; 定義5,一次指定多個鍵 (def currency-of(fn [{:strs [currency amount]}](println currency amount)));; 使用5 (currency-of {"currency" "US", "amount" 12});; 定義6,一次指定多個鍵 (def currency-of(fn [{:syms [currency amount]}](println currency amount)));; 使用6 (currency-of {'currency "US", 'amount 12});; 定義7,默認值 (def currency-of(fn [{:keys [currency amount] :or {currency "CHINA"}}](println currency amount)));; 使用7 (currency-of {:amount 100}) ;;=> 100CHINA;; 定義8,命名鍵值對 (def currency-of(fn [{:keys [currency amount] :as orig}](println (:currency orig))))(currency-of {'currency "US", 'amount 12}) ;;=> US可變入參(Variadic Functions)
通過&定義可變入參,可變入參僅能作為最后一個入參來使用
(def tap(fn [ns & more](println ns (first more))))(tap "user.core" "1" "2" "3") ;;=> user.core1命名入參(Named Parameters/Extra Arguments)
?通過組合可變入參和參數解構,我們可以得到命名入參
(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/nothingMultimethods
?Multi-Arity函數中我們可以通過入參數目來調用不同的函數實現,但有沒有一種如C#、Java那樣根據入參類型來調用不同的函數實現呢?clj/cljs為我們提供Multimethods這一殺技——不但可以根據類型調用不同的函數實現,還可以根據以下內容呢!
?想說"Talk is cheap, show me the code"嗎?在看代碼前,我們先看看到底Multimethods的組成吧
1.dispatching function
?用于對函數入參作操作,如獲取類型、值、運算入參關系等,然后將返回值作為dispatching value,然后根據dispatching value調用具體的函數實現。
2.method
?具體函數實現
3.hierarchy object
?存儲層級關系的對象,默認情況下所有相關的Macro和函數均采用全局hierarchy object,若要采用私有則需要通過(make-hierarchy)來創建。
還是一頭霧水?上示例吧!
示例1 —— 根據第二個入參的層級關系
示例2 -- 根據第一個入參的值
(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 -- 根據兩入參數值比較的大小
(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
;; 函數簽名 (remove-method multifn dispatch-val);; 示例 (remove-method area true)分發規則
?先對dispatching value和method的dispatching-value進行=的等于操作,若不匹配則對兩者進行isa?的層級關系判斷操作,就這樣遍歷所有注冊到該multimethod的method,得到一組符合的method。若這組method的元素個數有且僅有一個,則執行該method;若沒有則執行:default method,若還是沒有則拋異常。若這組method的元素個數大于1,且沒有人工設置優先級,則拋異常。
?通過prefer-method我們可以設置method的優先級
層級關系
?層級關系相關的函數如下:
;; 判斷層級關系 (isa? h? child parent) ;; 構造層級關系 (derive h? child parent) ;; 解除層級關系 (underive h? child parent) ;; 構造局部hierarchy object (make-hierarchy)上述函數當省略h?時,則操作的層級關系存儲在全局的hierarchy object中。
注意:層級關系存儲在全局的hierarchy object中時,Symbole、Keyword均要包含命名空間部分(即使這個命名空間并不存在),否則會拒絕。
另外還有parent、ancestors和descendants
(derive `c `p) (derive `p `pp);; 獲取父層級 (parent `c) ;;=> `p ;; 獲取祖先 (ancestors `c) ;;=> #{`p `pp} ;; 獲取子孫 (descendants `pp) ;;=> #{`p `c}局部層級關系
?通過(make-hierarchy)可以創建一個用于實現局部層級關系的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)注意:局部層級關系中的Symbol和Keyword是可以包含也可以不包含命名空間部分的哦!
Condition Map
?對于動態類型語言而言,當入參不符合函數定義所期待時,是將入參格式化為符合期待值,還是直接報錯呢?我想這是每個JS的工程師必定面對過的問題。面對這個問題我們應該分階段分模塊來處理。
?而clj/cljs函數中的condition map就是為我們在開發階段提供對函數入參、函數返回值合法性的斷言能力,讓我們盡早發現問題。
示例
(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中我們可以直接指向函數的入參,在post-exprs中則通過%來指向函數的返回值。
?雖然增加函數執行的前提條件,而且可以針對函數的值、關系、元數據等進行合法性驗證,但依舊需要在運行時才能觸發驗證(這些不是運行時才觸發還能什么時候能觸發呢?)。對動態類型語言天然編譯期數據類型驗證,我們可以通過core.typed這個項目去增強哦!
總結
?現在我們可以安心把玩函數了,oh yeah!
尊重原創,轉載請注明來自:http://www.cnblogs.com/fsjohnhuang/p/7137597.html ^_^肥仔John
總結
以上是生活随笔為你收集整理的(cljs/run-at (JSVM. :all) 细说函数)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 简述园路的功能作用_智能式多功能电表解析
- 下一篇: gradle idea java ssm