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

注意:

  1. Lambda表達式的函數體只允許使用一個表達式,因此要通過special formdo來運行多個表達式;
  2. 入參symbol為%1,%2,...%n,當有且只有一個入參時可以使用%來指向該入參。

Metadata——為函數附加元數據

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

;; 定義
(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>}

若只打算設置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/nothing

Multimethods

?Multi-Arity函數中我們可以通過入參數目來調用不同的函數實現,但有沒有一種如C#、Java那樣根據入參類型來調用不同的函數實現呢?clj/cljs為我們提供Multimethods這一殺技——不但可以根據類型調用不同的函數實現,還可以根據以下內容呢!

  1. 類型
  2. 屬性
  3. 元數據
  4. 入參間關系

?想說"Talk is cheap, show me the code"嗎?在看代碼前,我們先看看到底Multimethods的組成吧
1.dispatching function
?用于對函數入參作操作,如獲取類型、值、運算入參關系等,然后將返回值作為dispatching value,然后根據dispatching value調用具體的函數實現。

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

2.method
?具體函數實現

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

3.hierarchy object
?存儲層級關系的對象,默認情況下所有相關的Macro和函數均采用全局hierarchy object,若要采用私有則需要通過(make-hierarchy)來創建。

還是一頭霧水?上示例吧!
示例1 —— 根據第二個入參的層級關系

(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 -- 根據第一個入參的值

(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的優先級

(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) 這里就不會出現多個匹配的method
(prefer-method `a `b)
(test `c) ;;=> `a

層級關系

?層級關系相關的函數如下:

;; 判斷層級關系
(isa? h? child parent)
;; 構造層級關系
(derive h? child parent)
;; 解除層級關系
(underive h? child parent)
;; 構造局部hierarchy object
(make-hierarchy)

上述函數當省略h?時,則操作的層級關系存儲在全局的hierarchy object中。
注意:層級關系存儲在全局的hierarchy object中時,Symbole、Keyword均要包含命名空間部分(即使這個命名空間并不存在),否則會拒絕。

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

另外還有parentancestorsdescendants

(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的工程師必定面對過的問題。面對這個問題我們應該分階段分模塊來處理。

  1. 開發階段,對于內核模塊,讓問題盡早暴露;
  2. 生產階段,對于與用戶交互的模塊,應格式化輸入,并在后臺記錄跟蹤問題。
    ?而clj/cljs函數中的condition map就是為我們在開發階段提供對函數入參、函數返回值合法性的斷言能力,讓我們盡早發現問題。
(fn name [params*] condition-map? exprs*)
(fn name ([params*] condition-map? exprs*)+); condition-map? => {:pre [pre-exprs*]
;                    :post [post-exprs*]}
; pre-exprs 就是作為一組對入參的斷言
; post-exprs 就是作為一組對返回值的斷言

示例

(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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/454819.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/454819.shtml
英文地址,請注明出處:http://en.pswp.cn/news/454819.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Request的getHeader()和getParameter()的區別

區別是&#xff1a;一個是獲得HTTP頭信息,一個是獲得表單參數值。轉載于:https://www.cnblogs.com/pxffly/p/7460514.html

gcc中的內嵌匯編語言(Intel i386平臺)

gcc中的內嵌匯編語言&#xff08;Inteli386平臺&#xff09; 一.聲明 雖然Linux的核心代碼大部分是用C語言編寫的&#xff0c;但是不可避免的其中還是有一部分是用匯編語言寫成的。有些匯編語言代碼是直接寫在匯編源程序中的&#xff0c;特別是Linux的啟動代碼部分&#xff1b…

數據庫學習,樹形結構的數據庫表Schema設計方案

2019獨角獸企業重金招聘Python工程師標準>>> 程序設計過程中&#xff0c;我們常常用樹形結構來表征某些數據的關聯關系&#xff0c;如企業上下級部門、欄目結構、商品分類等等&#xff0c;通常而言&#xff0c;這些樹狀結構需要借助于數據庫完成持久化。然而目前的各…

[轉載] 手工制作Win7 OEM版

只要往微軟MSDN原版ISO的sources目錄加個“$OEM$”文件夾&#xff0c;再刪除sources下面的ei.cfg文件就可以了。 來源&#xff1a;http://zxkh19501.blog.163.com/blog/static/1237851792010629113427594/轉載于:https://www.cnblogs.com/784040932/p/win7oem.html

mysql dbo_mysql-雙重分組

我的表有兩列&#xff1a;名稱和等級.看起來像這樣&#xff1a;NAME | GRADEAdam | 1Adam | 2Adam | 2Adam | 3Frank | 2Frank | 1現在,我想創建如下所示的視圖&#xff1a;NAME | GRADE 1 | GRADE 2 | GRADE 3Adam | 1 | 2 | 1Frank | 1 | 1 | 0我寫了這個&#xff1a;SELECT …

課堂作業整理三 (集合:list接口)

集合中 list的方法列表&#xff08;Arraylist和Linkedlist&#xff09; 方法名功能說明ArrayList()構造方法&#xff0c;用于創建一個空的數組列表add&#xff08;E&#xff0c;e&#xff09;將指定的元素添加到此列表的尾部get&#xff08;int index&#xff09;返回此列表中指…

LINUX系統移植(史上最全最細,強烈推薦)

Linux系統移植 目 錄 第一部分 前言...................................................................................................................................8 1 硬件環境................................................................................…

The serializable class XXX does not declare a static final serialVersionUID field of type long的警告...

原文: http://blog.csdn.net/ultrakang/article/details/41820543轉載于:https://www.cnblogs.com/Baronboy/p/7465508.html

Ubuntu17.04 之 systemd 設置開機啟動

Ubuntu從16.04開始不再使用 initd 管理系統&#xff0c;改用 systemd。 和 Centos 一樣&#xff0c;升級到 Centos7 之后使用 systemd 替代 init.d 為了像以前一樣&#xff0c;在/etc/rc.local中設置開機啟動程序&#xff0c;需要以下幾步&#xff1a; 1、鏈接文件 systemd 默…

replaceselection();java'_Java JTextComponent.replaceSelection方法代碼示例

import javax.swing.text.JTextComponent; //導入方法依賴的package包/類public void actionPerformed(final ActionEvent evt, final JTextComponent target) {if (target ! null) {if (!target.isEditable() || !target.isEnabled()) {target.getToolkit().beep();return;}Ed…

Systemd 入門教程之命令篇

Systemd 是 Linux 系統工具&#xff0c;用來啟動守護進程&#xff0c;已成為大多數發行版的標準配置。 本文介紹它的基本用法&#xff0c;分為上下兩篇。今天介紹它的主要命令&#xff0c;下一篇介紹如何用于實戰。 一、由來 歷史上&#xff0c;Linux 的啟動一直采用init進程。…

GCC生成的匯編代碼

假設我們寫了一個C代碼文件 code.c包含下面代碼&#xff1a; int accum 0; int sum(int x, int y) { int t x y; accum t; return t; } 這是用echo命令輸入源碼的效果&#xff0c;簡單的就是最好的&#xff1a;&#xff09;一、查看GCC生成的匯編代碼在命令行…

php __FILE__,__CLASS__等魔術變量,及實例

php __FILE__,__CLASS__等魔術變量,及實例 今天看到一個魔術變量&#xff0c;是以前沒見過的&#xff0c;__DIR__&#xff0c;我查了查&#xff0c;發現原來是php5.3新增的&#xff0c;順便舉幾個例子&#xff0c;解釋一下php的魔術變量 1&#xff0c;__FILE__ 文件的完整路徑和…

java虛方法和抽象方法_虛方法和抽象方法--基礎回顧

抽象方法是只有定義、沒有實際方法體的函數&#xff0c;它只能在抽象函數中出現&#xff0c;并且在子類中必須重寫&#xff1b;虛方法則有自己的函數體&#xff0c;已經提供了函數實現&#xff0c;但是允許在子類中重寫或覆蓋。重寫的子類虛函數就是被覆蓋了。抽象方法使用abst…

jQuery高度及位置操作

1. 獲取滑輪位置&#xff0c;scrolltop:上下滾動的意思。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body><div style"height:100px;width:10…

you have mixed tabs and spaces fix this

http://blog.csdn.net/tonyyan19781/article/details/60882443Vs2013 IDE下&#xff0c;編輯C的工程源碼&#xff0c;在打開文件的時候&#xff0c;會出現 “ you have mixed tabs and spaces fix this ”&#xff0c; 然后給出三個選項 Tabify、Untabify、Dont show again。尤…

Systemd 入門教程之實戰篇

一、開機啟動 對于那些支持 Systemd 的軟件&#xff0c;安裝的時候&#xff0c;會自動在/usr/lib/systemd/system目錄添加一個配置文件。 如果你想讓該軟件開機啟動&#xff0c;就執行下面的命令&#xff08;以httpd.service為例&#xff09;。$ sudo systemctl enable httpd上…

從VC++到GCC移植:談兩者的語法差異

從VC到GCC移植&#xff1a;談兩者的語法差異 許式偉 &#xff08;版權聲明&#xff09; 2007-1-28 類型引用 template <classT>classFoo { typedef T::SomeType SomeType; };這段代碼在VC中一點問題也沒有&#xff0c;但是GCC并不允許&#xff0c;因為它不知道T::S…

牛客網Java刷題知識點之關鍵字static、static成員變量、static成員方法、static代碼塊和static內部類...

不多說&#xff0c;直接上干貨&#xff01; 牛客網Java刷題知識點之關鍵字static static代表著什么 在Java中并不存在全局變量的概念&#xff0c;但是我們可以通過static來實現一個“偽全局”的概念&#xff0c;在Java中static表示“全局”或者“靜態”的意思&#xff0c;用來修…

30天自制操作系統(二)匯編語言學習與Makefile入門

1 介紹文本編輯器這部分可直接略過2 繼續開發helloos.nas中核心程序之前的內容和啟動區以外的內容先不講了&#xff0c;因為還涉及到一些軟盤方面的知識。然后來講的是helloos.nas這個文件; hello-os ; TAB4ORG 0x7c00 ; 指明程序的裝載地址; 以下這部分記錄…