Clojure 學習筆記 :5 我來組成函數(shù)~

Clojure 零基礎(chǔ) 學習筆記 函數(shù)式編程


函數(shù)即是值。

終于甘凭,我們要介紹 Clojure 中最重要的部分了。
在此之前丹弱,你已經(jīng)見到好多 Clojure 自帶的函數(shù)了,比如打印函數(shù) print 家族躲胳,給值取名的函數(shù) def蜓洪,還有一些對集合操作的函數(shù) first assoc

你可能已經(jīng)熟記了函數(shù)的幾個特征:

  • 函數(shù)都有返回值
  • 函數(shù)可以接受參數(shù)隆檀,也可以不接受參數(shù)
  • 如果函數(shù)改變了外部世界的一些狀態(tài)(如修改了某個值,或者改變了屏幕上的顯示效果)粹湃,我們稱這個函數(shù)存在副作用

小貼士:始終要記住,在 Clojure 中为鳄,函數(shù)總是能被求值,函數(shù)的值即為他的返回值济赎。
所以當存在好多小括號進行嵌套的時候,我們可以分別求出內(nèi)層函數(shù)的值司训,然后再一層層的計算出整體的值液南,從而得到程序的結(jié)果。

雖然 Clojure 內(nèi)置了豐富的函數(shù)滑凉,但很多情況下喘帚,我們還是需要創(chuàng)造出自己的函數(shù),或者組裝拼接一些現(xiàn)有函數(shù)吹由,使之更為強大。


定義函數(shù)

首先朱嘴,讓我們從創(chuàng)造一個簡單的函數(shù)開始說起,
我們可以使用 fn 函數(shù)來創(chuàng)建一個屬于我們自己的函數(shù)萍嬉,
我們這個自定義函數(shù)的功能是,它接受一個數(shù)字作為參數(shù)壤追,返回這個數(shù)字加上10之后的值。

=> (fn [x]
     (+ x 10))
#<core$eval8059$fn__8060 my_clojure_study.core$eval8059$fn__8060@20302ac5>

看起來和我們之前接觸過的 let 函數(shù)有點類似行冰,沒錯,fn 函數(shù)隱式使用了 let 函數(shù)悼做。也就是你可以在那個中括號里面使用之前我們學到的關(guān)于解構(gòu)的知識!

  • fn 函數(shù)的第一個參數(shù)贿堰,用中括號包圍辙芍,用來表示我們自定義函數(shù)的參數(shù)列表故硅。
    比如這里的 x
  • fn 函數(shù)接下來的參數(shù)是一系列表達式纵搁,它們會被依次執(zhí)行。最后一個被執(zhí)行的表達式則作為自定義函數(shù)的返回值腾誉。
  • fn 函數(shù)本身的返回值,是我們的自定義函數(shù)本身利职。這里返回的是一串看似無意義的隨機函數(shù)名,我們無視它猪贪。(還記得我們一開始所說的,“函數(shù)即是值么”热押,這意味著我們可以把函數(shù)作為返回值來返回N骺)

我們可以這樣來使用我們創(chuàng)造出的函數(shù):[1]

=> ((fn [x] (+ x 10)) 1)
11

是不是有點暈?
不要急拥褂,我們來仔細看一下。
首先饺鹃,(fn [x] (+ x 10)) 的值是一個函數(shù),這個函數(shù)的功能就是我們自定義的 --- 它接受一個數(shù)字尤慰,返回這個數(shù)字加上10的結(jié)果。
由于我們的自定義函數(shù)放在了外層括號的第一個位置伟端,所以它會當作函數(shù)來執(zhí)行,而之后的數(shù)字1责蝠,則作為我們自定義函數(shù)的參數(shù)党巾。
數(shù)字1傳到了 x 的位置霜医,所以此時 x 的值就成了 1。
執(zhí)行自定函數(shù)內(nèi)的代碼 (+ x 10)肴敛,由于 x 現(xiàn)在為1,此時我們的代碼就等價于 (+ 1 10)医男。
計算出結(jié)果11,返回镀梭。

還是不明白?不用擔心报账,這次我們使用之前學過的 def 函數(shù)來給我們的“新朋友”取一個名字研底。

=> (def plus-ten (fn [some-num] (+ some-num 10)))
#'my-clojure-study.core/plus-ten

(我們把我們的自定義參數(shù)的參數(shù)名 x 換了一個新名字榜晦,這樣并不會影響函數(shù)的功能,但是會增加少許可讀性琐凭。)

現(xiàn)在我們的自定義函數(shù)就有了一個新名字 --- plus-ten
然后我們就可以這樣來使用它了:

=> (plus-ten 24)
34

看起來工作的還不錯。

由于我們經(jīng)常需要定義諸如此類的有名字的自定義函數(shù)统屈,
所以 Clojure 給我們內(nèi)置了一個 deffn 的合體函數(shù)! --- defn
很好記吧愁憔,它是這么來使用的:

=> (defn plus-ten
     [some-num]
     (+ some-num 10))
#'my-clojure-study.core/plus-ten

=> (plus-ten 45678)
45688

這里我們又使用了換行,來把參數(shù)列表和函數(shù)體分隔開吨掌,顯得程序?qū)哟胃忧逦?br> 如你所見:

  • defn 函數(shù)的第一個參數(shù)是我們給自定義函數(shù)取的名字。
  • 第二個參數(shù)用中括號包圍膜宋,里面是我們自定義函數(shù)的參數(shù)列表。
  • 之后的代碼是我們自定義函數(shù)的主體部分秋茫,并把最后一個作為自定義函數(shù)的返回值(這里只有一個史简,所以就把它作為返回值)肛著。

使用起來非常方便。


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

之前我們提到枢贿,fndefn 都隱式使用了 let,所以它們都支持解構(gòu)局荚。
下面我們來看一下具體的例子。

自定義函數(shù)所接受的參數(shù)可以不是單調(diào)的數(shù)字之類耀态,我們可以直接接受一個列表。
假如我們想寫一個自定義函數(shù)茫陆,它的功能是:
接受一個列表金麸,把這個列表中第一個值和第三個值相加挥下,并把結(jié)果返回。
例如桨醋,如果接受 (1 2 4) 或者 [1 2 4],它應(yīng)該返回5喜最。

我們可以這樣來實現(xiàn):

=> (defn plus-first-and-third
     [[f _ t]]
     (+ f t))
#'my-clojure-study.core/plus-first-and-third
=> (def some-collection [1 2 4])
#'my-clojure-study.core/some-collection

=> (plus-first-and-third some-collection)
5

這里和我們前面學的解構(gòu)形式略有不同。defn 函數(shù)的解構(gòu)首先直接對應(yīng)了參數(shù)傳入的位置,如此例中的第一參數(shù)迷雪,然后對這個位置再寫一個中括號進行解構(gòu)。
舉個栗子章咧。
如果傳入了三個參數(shù) [x y z],我們要解構(gòu) y赁严,
那么就可以寫成 [x [first-y second-y] z]
這個例子中疼约,plus-first-and-third 函數(shù)接受一個參數(shù),
而這個參數(shù)又被解構(gòu)成三個值 --- f _ t程剥。

還有一種常見的解構(gòu)形式是劝枣,解構(gòu)剩余參數(shù)倡缠。
我們在綁定與解構(gòu)一文的最后,介紹了使用 & 給剩余元素取名.
這使得我們可以創(chuàng)建不定長的昙沦,可變參數(shù)列表。
比如我們創(chuàng)建這樣一個函數(shù)盾饮,
它接受大于三個參數(shù),打印前兩個參數(shù)的和的值丘损,以及打印后續(xù)參數(shù)。返回 nil徘钥。
我們可以這樣實現(xiàn)它:

=> (defn print-plus-f-s
     [f s & rest-num]
     (println (+ f s))
     (println rest-num))
#'my-clojure-study.core/print-plus-f-s

多于3個的參數(shù)會被作為一個 list來綁定到 rest-num衔蹲。
現(xiàn)在看看它是如何工作的:

=> (print-plus-f-s 1 2 3 4 5)
3
(3 4 5)
nil

它打印了1和2的和呈础,并且打印了后續(xù)數(shù)字的 list 形式。
由于最后一個表達式的值被作為自定義函數(shù)的值而钞,而 println 函數(shù)的值始終為 nil,所以我們的 print-plus-f-s 函數(shù)的返回值始終為 nil臼节。

實際上珊皿,剛才我們討論的剩余參數(shù)一樣可以繼續(xù)被解構(gòu):

=> (defn print-plus-f-s
     [f s & [third-num & rest-num]]
     (println (+ f s))
     (println third-num)
     (println rest-num))
#'my-clojure-study.core/print-plus-f-s

=> (print-plus-f-s 1 2 3 4 5)
3
3
(4 5)
nil

解構(gòu)是一個非常實用的技能,但是多層的解構(gòu)對于初學者來說理解起來較為困難蟋定。
不過不要灰心,稍微復雜的例子留給大家仔細思考一下垢夹。
試著自己寫出一些代碼并運行是幫助學習的有效途徑维费。
如果實在是找不到解答,可以在下方留言犀盟。


高階函數(shù)

函數(shù)和數(shù)字而晒、字符串阅畴、列表之類的數(shù)據(jù)一樣,可以直接作為參數(shù)或者返回值來進行傳遞贱枣。
我們把接受函數(shù)為參數(shù),或者把函數(shù)作為返回值的函數(shù)稱之為高階函數(shù)纽哥。
使用高階函數(shù),我們就能組裝出更為強大和靈活的武器春塌。

例如我們剛才學到的 fn 函數(shù)晓避,就是一個高階函數(shù)只壳,因為它的返回值是我們的自定義函數(shù)。
我們同樣可以創(chuàng)造出屬于我們自己的高階函數(shù)吼句。

剛才我們創(chuàng)建了一個 plus-ten 的函數(shù),它可以把一個數(shù)字加上10惕艳,并把這個結(jié)果返回過來。但是此時你的老板覺得我們還需要一個把一個數(shù)字減去10的函數(shù)尔艇。(雖然這個例子很幼稚尔许,你大可以直接使用減法终娃,但是我們只是以此來說明高階函數(shù)的簡單用法。)

你可以再創(chuàng)造一個函數(shù):

=> (defn subtract-ten
     [some-num]
     (- some-num 10))
#'my-clojure-study.core/subtract-ten

但你可惡的老板(或者你的客戶)又要求你寫一個乘以10的函數(shù),鬼知道他會不會再讓你寫更多的無聊函數(shù)柠新。
如果有一個函數(shù)可以根據(jù)參數(shù)來生成不同的函數(shù),那你就可以從無聊的重復代碼上解脫了辉巡。
高階函數(shù)就可以用來做這個:

=> (defn operate-ten
     [operate]
     (fn [x] (operate x 10)))
#'my-clojure-study.core/operate-ten

這是一個可以生成函數(shù)的函數(shù),它接受一個函數(shù)作為參數(shù)郊楣,同時返回一個函數(shù)。
而它所返回的函數(shù)净蚤,則使用 operate-ten 所接受的那個參數(shù)來作為返回函數(shù)的執(zhí)行部分的運算符。

說人話

好吧今瀑,我們直接看看它是怎么工作的:

=> (def subtract-ten (operate-ten -))
#'my-clojure-study.core/subtract-ten

=> (subtract-ten 9)
-1
  • 首先程梦,我們給這個能生成函數(shù)的函數(shù)傳參數(shù) -橘荠,也就是減法函數(shù)。
  • 然后哥童,operate 這個值就變成了我們的減法函數(shù) -
    如果時間停止如蚜,展開 operate-ten 這個高階函數(shù),那么它是這個樣子的:
=> (defn operate-ten
     [-]
     (fn [x] (- x 10)))

沒錯错邦!值就這樣簡單的被傳進來的參數(shù)替換了而已探赫!

  • 時間繼續(xù)流逝撬呢,現(xiàn)在它返回了 (fn [x] (- x 10))。這正是我們想要的函數(shù)魂拦!
  • 然后我們使用 def 函數(shù)來命名這個由函數(shù)生成的函數(shù)。
  • 使用這個函數(shù)芯勘,就如同之前一樣。

哈哈荷愕,我們再也不用自己從頭寫一次代碼了衡怀!
這個時候你的老板要求你寫一個把一個數(shù)字乘以10的函數(shù),你就可以這樣來使用我們的高階函數(shù):

=> (def multiply-ten (operate-ten *))
#'my-clojure-study.core/multiply-ten

完事兒 =v=


僅此而已么抛杨?
當然不是!
我們甚至可以讓這個高階函數(shù)生成一個功能為:
“生成一個元素和10組成的有兩個元素的列表”的函數(shù)怖现。

=> (def list-with-ten-end (operate-ten list))
#'my-clojure-study.core/list-with-ten-end

=> (list-with-ten-end "我是第一個元素")
("我是第一個元素" 10)

或者:
“生成一個以10結(jié)尾的字符串”的函數(shù)。

=> (def string-with-ten-end (operate-ten str))
#'my-clojure-study.core/string-with-ten-end

=> (string-with-ten-end 123)
12310
=> (string-with-ten-end "head-")
head-10

小貼士:str 函數(shù)可以把它的參數(shù)拼接成一個字符串屈嗤,并以此作為它的返回值潘拨。
例子:

=> (str "hello" "world!" 10)
helloworld!10

僅僅因為恢共,我們向 operate-ten 所傳遞的函數(shù)的不同!(這里是 liststr


此次我們學習了作為 Clojure 中“頭等公民”的函數(shù)讨韭。
了解了自定義函數(shù)的幾種聲明方式捣鲸,以及參數(shù)列表的常見解構(gòu)形式渴逻。
最后我們窺見了函數(shù)式編程中高階函數(shù)的概念祈远,并體驗了它所帶來的靈活性蔫耽。

最后的最后控乾,我希望你能仔細理解本文中的代碼幔欧,最好實際運行一下,并嘗試修改它們礁蔗。


  1. 這里你可能注意到,我們使用了 x 來表示我們的參數(shù)浴井。在其他語言中,這種命名風格通常是不被建議的磺浙。但是在 Clojure 中洪囤,你可以使用類似 x y a b 之類的名稱撕氧,來表示一個非常通用的類型,也就是表示這個函數(shù)支持各種類型的值伦泥。 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末何暮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子海洼,更是在濱河造成了極大的恐慌,老刑警劉巖富腊,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赘被,居然都是意外死亡,警方通過查閱死者的電腦和手機民假,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來羊异,“玉大人,你說我怎么就攤上這事野舶∫准#” “怎么了平道?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長一屋。 經(jīng)常有香客問我,道長冀墨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任轧苫,我火速辦了婚禮,結(jié)果婚禮上含懊,老公的妹妹穿的比我還像新娘身冬。我一直安慰自己岔乔,他們只是感情好,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布雏门。 她就那樣靜靜地躺著掸掏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宙帝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天步脓,我揣著相機與錄音,去河邊找鬼靴患。 笑死,一個胖子當著我的面吹牛鸳君,可吹牛的內(nèi)容都是我干的农渊。 我是一名探鬼主播或颊,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼饭宾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起看铆,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤盛末,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后悄但,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棠隐,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡檐嚣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嚎京。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡鞍帝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出帕涌,到底是詐尸還是另有隱情摄凡,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布亲澡,位于F島的核電站,受9級特大地震影響床绪,放射性物質(zhì)發(fā)生泄漏客情。R本人自食惡果不足惜会涎,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望末秃。 院中可真熱鬧,春花似錦练慕、人聲如沸惰匙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽劲阎。三九已至,卻和暖如春悯仙,著一層夾襖步出監(jiān)牢的瞬間龄毡,已是汗流浹背锡垄。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留货岭,地道東北人路操。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓屯仗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親丈牢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容