Clojure 學(xué)習(xí)筆記 :3 綁定與解構(gòu)

Clojure 零基礎(chǔ) 學(xué)習(xí)筆記 綁定 解構(gòu)


是時(shí)候給我們的值取個(gè)名字了臂容!

綁定

在之前的學(xué)習(xí)中仁堪,我們學(xué)會(huì)了如何使用簡單的數(shù)據(jù)結(jié)構(gòu) --- list 和 vector
但是每次使用的時(shí)候常潮,我們都需要重新寫一遍我們的元素內(nèi)容
使用起來非常麻煩

于是 Clojure 提供了“取名”的功能:def 函數(shù) (即 define保礼,定義)
def 函數(shù)的第一個(gè)參數(shù)是你想給你的值取的名字镐依,第二個(gè)參數(shù)是你的值(表達(dá)式)须喂。

=> (def my-items ["短劍" "火把" "汽油"])
#'user/my-items

這樣以來我們就可以更為方便的使用我們的 vector 了

=> (first my-items)
短劍

(注意吁断,def 所定義的內(nèi)容不是變量,會(huì)在之后的文章中討論這一問題)

我們觀察它的返回值 #'user/my-items 它表示我們成功地在 user 空間中創(chuàng)建了一個(gè) my-items
user 空間是 REPL 啟動(dòng)后默認(rèn)的命名空間(namespace)镊折,它表示我們的 my-items 只在 user 中才能使用

(命名空間的作用和用法胯府,日后再說…現(xiàn)在你可以把它理解為一個(gè)文件夾,我們是在 user 這個(gè)“文件夾”下面新建了一個(gè) my-items恨胚,如果你去另外的文件夾里面訪問骂因,即使那個(gè)文件夾也有一個(gè)叫 my-items 的東西,那它也不是我們這個(gè) my-items

這里我們簡單提一下 Clojure 的命名規(guī)范
我們使用全小寫字母減號間隔來表示 Clojure 里的一個(gè)值的名稱
如:a-beautiful-world items lovely-girl


使用 def 函數(shù)定義一個(gè)值赃泡,這個(gè)值在整個(gè) user 空間都可以被訪問

下面我們介紹的 let 函數(shù)寒波,它被稱為“局部綁定”
(在其它文章中也稱之為“本地綁定”、“本地值”升熊、“l(fā)et 綁定”俄烁。指的都是我們這里介紹的內(nèi)容)
之所以叫它“局部綁定”,是因?yàn)槭褂盟鼇砻拿Q级野,只在 let 的括號范圍內(nèi)才能使用

=> (let [my-items ["短劍" "火把" "汽油"]]
        (println my-items))
[短劍 火把 汽油]
nil

我們在第二和第三個(gè)參數(shù)之間換行页屠,這并不影響程序的執(zhí)行,無論你是否換行
但這增加了程序的可讀性[1]
養(yǎng)成良好的編程風(fēng)格蓖柔,適當(dāng)?shù)目s進(jìn)和換行會(huì)增加程序的可讀性

let 函數(shù)的使用比我們之前所學(xué)的函數(shù)要復(fù)雜一點(diǎn)

  • 首先辰企,它的第一個(gè)參數(shù)是一個(gè) vector (使用中括號包圍),中括號里元素個(gè)數(shù)必須為偶數(shù)况鸣,
    因?yàn)橹欣ㄌ柪锩恳粚υ乇硎?“給值取的名字” - “值”(key-value)牢贸,
    也就是說,我們可以一次性地給多個(gè)值取名字

  • 然后镐捧,它之后的參數(shù)是多個(gè)表達(dá)式潜索,
    表達(dá)式會(huì)被 Clojure 依次執(zhí)行

  • 最后, let 函數(shù)的返回值等于它最后執(zhí)行的表達(dá)式的值

我們來觀察更多的例子

=> (let [my-items ["短劍" "火把" "汽油"] my-coins [128]]
     (print my-coins)
     my-items)
[128]
["短劍" "火把" "汽油"]

=> (let [my-items ["短劍" "火把" "汽油"] my-coins [128]]
     my-items
     my-coins
     (print my-coins))
[128]
nil

=> (let [my-items ["短劍" "火把" "汽油"] my-coins [128]]
     my-items
     my-coins
     (print my-coins))
   (print my-coins) ;嘗試在 let 的括號外訪問 my-items
[128]
nil
CompilerException java.lang.RuntimeException: Unable to resolve symbol: my-coins in this context
  • 第一段代碼展示了如何使用 let 來一次性地定義兩個(gè)值 --- my-itemsmy-coins
    然后在 let 的括號范圍內(nèi)懂酱,我們依次執(zhí)行了兩句表達(dá)式竹习,而且我們在表達(dá)式之間使用換行以增加可讀性
    第一句表達(dá)式輸出 my-coins 的值
    第二句表達(dá)式直接使用 my-items,沒有使用括號包圍列牺,這表示我們直接使用它的值由驹,而不是把它作為函數(shù)
    由于 let 函數(shù)規(guī)定最后被執(zhí)行的表達(dá)式的值為 let 函數(shù)的返回值,所以此例中 let 函數(shù)的返回值為 my-items 的值
  • 而第二段代碼與第一段不同的是,它最后才執(zhí)行了 print 函數(shù)
    我們可以看到蔓榄, print 函數(shù)的副作用效果出現(xiàn) --- my-coins 的值 [128] 被打印出來并炮,
    但是由于 print 函數(shù)的返回值始終為 nil
    所以此例中 let 函數(shù)的返回值為最后執(zhí)行的表達(dá)式的值 --- 即 print 函數(shù)的值,雖然在它之前我們對 my-itemsmy-coins 的值進(jìn)行的訪問甥郑,但由于沒有執(zhí)行打印到屏幕上的操作逃魄,我們無法觀察到它們的值

  • 在第三段代碼中,我們嘗試在 let 函數(shù)的括號外訪問 my-coins澜搅,結(jié)果可想而知伍俘,錯(cuò)誤信息表示:Unable to resolve symbol: my-coins (無法理解符號 my-coins
    因?yàn)?let 函數(shù)給值取的名字的有效性只在它的“勢力范圍”之內(nèi),即只能在它前后括號的范圍內(nèi)使用

很多函數(shù)隱式地使用了 let勉躺,在今后的 “定義屬于你自己的函數(shù)” 章節(jié)中癌瘾,這里所學(xué)習(xí)到的有關(guān) let 的知識就能夠派上更大的用場了


解構(gòu)

在前一個(gè)章節(jié)中,我們學(xué)習(xí)了如何訪問一個(gè)集合中的元素
但如果每次都這樣使用饵溅,顯得繁瑣而無聊

=> (def my-items ["短劍" "火把" "汽油"])
#'user/my-items

=> (first my-items)
短劍
=> (rest my-items)
("火把" "汽油") ;復(fù)習(xí)一下妨退,rest 函數(shù)返回除 first 之外剩余元素的 list 形式

尤其是我們想給一個(gè)集合里面的元素都取一個(gè)新名字時(shí)

=>(def first-item (first my-items))
#'user/first-item

=>(def rest-item (rest my-items))
#'user/rest-item

或者在訪問一個(gè)多層嵌套的集合時(shí)

=> (def my-coins 256)
#'user/my-coins
=> (def my-bag [my-items my-coin])
#'user/my-bag

=> (nth (first my-bag) 2)
汽油

可以想象如果一個(gè)集合里面的元素非常多,或者嵌套層數(shù)非常多的時(shí)候蜕企,這種方式效率十分低下
不過咬荷,當(dāng)你的嵌套層數(shù)非常多的時(shí)候,就該反思一下你的設(shè)計(jì)了
(可能寫出來的代碼你自己也讀不懂)

幸虧 let 函數(shù)給我們提供了一個(gè)誘人的訪問集合元素的方式

=> (def my-bag [["短劍" "火把" "汽油"] 256])
#'user/my-bag
=> (let [[items coins] my-bag]
     (println "你所擁有的裝備:" items) ;復(fù)習(xí)一下轻掩,print 函數(shù)家族可以接受多個(gè)參數(shù)幸乒,并依次輸出他們的值
     (println "你所擁有的金幣:" coins))
你所擁有的裝備: [短劍 火把 汽油]
你所擁有的金幣: 256
nil

我們稱之為 解構(gòu)
因?yàn)樗梢郧逦乇憩F(xiàn)原結(jié)構(gòu)的樣子
如果不使用解構(gòu),就會(huì)長成這樣:

=> (let [items (first my-bag) coins (second my-bag)]
     (println "你所擁有的裝備:" items)
     (println "你所擁有的金幣:" coins))
你所擁有的裝備: [短劍 火把 汽油]
你所擁有的金幣: 256
nil

解構(gòu)的具體用法十分簡單
你只需要
let 函數(shù)的第二個(gè)參數(shù)里

  1. 把原來 “給值取的名字” 的位置唇牧,寫成一個(gè) vector 的形式(即用中括號包圍)
  2. 原來填寫 “值” 的位置罕扎,寫上你要解構(gòu)的集合
(let [[你給集合里元素取的新名字] 你要解構(gòu)的集合])

如果我們把原結(jié)構(gòu)和解構(gòu)形式放在一起觀察的話

my-bag [["短劍" "火把" "汽油"]   256 ]
       [      items           coins] my-bag

看起來像是把定義集合倒過來寫一樣
這樣我們就在 let 綁定里面給這個(gè)兩個(gè)元素取了一個(gè)名字:

  • 集合 my-bag 的第一個(gè)元素取名為 items
  • 集合 my-bag 的第二個(gè)元素取名為 coins

而且,這個(gè)對應(yīng)關(guān)系真實(shí)反映了元素的位置丐重。


我們可以使用解構(gòu)從集合中取部分元素

=> (def my-bag [["短劍" "火把" "汽油"] 512 2])
#'user/my-bag

=> (let [[items silver-coin] my-bag]
        (println "你所擁有的裝備:" items)
        (println "你所擁有的銀幣:" silver-coin))
你所擁有的裝備: [短劍 火把 汽油]
你所擁有的銀幣: 512
nil

上面的例子中壳影,雖然我們的 my-bag 有三個(gè)元素,但是解構(gòu)可以只拿取前兩個(gè)
看起來就像這樣

my-bag [  ["短劍" "火把" "汽油"]     512        2]
       [        items           silver-coin    ]    my-bag

如果你只想要物品和金幣弥臼,你可以這樣來操作

=> (let [[items silver-coin gold-coin] my-bag]
        (println "你所擁有的裝備:" items)
        (println "你所擁有的金幣:" gold-coin))
你所擁有的裝備: [短劍 火把 汽油]
你所擁有的金幣: 2
nil

是不是看起來有點(diǎn)傻,給它取了名字卻沒有使用根灯。
不過径缅,如果我們不關(guān)心銀幣,那么我們可以給它隨便扔一個(gè)名字
比如 sth-I-don't-care
不過 Clojure 規(guī)范更傾向于使用短下劃線 _ 來命名你不感興趣的名稱

=> (let [[items _ gold-coin] my-bag]
        (println "你所擁有的裝備:" items)
        (println "你所擁有的金幣:" gold-coin))
你所擁有的裝備: [短劍 火把 汽油]
你所擁有的金幣: 2
nil

其實(shí)它只也是一個(gè)可以正常使用的名字而已

=> (let [[items _ gold-coin] my-bag]
        (println "你所擁有的裝備:" items)
        (println "你所擁有的銀幣:" _)
        (println "你所擁有的金幣:" gold-coin))
你所擁有的裝備: [短劍 火把 汽油]
你所擁有的銀幣: 512
你所擁有的金幣: 2
nil

如果你使用重復(fù)的名字烙肺,比如使用多個(gè) _ 來忽視掉你不關(guān)心的內(nèi)容
那么后面的值會(huì)把前面的值覆蓋掉

=> (let [[items _ _] my-bag]
        (println "你所擁有的裝備:" items)
        (println "你所擁有的銀幣:" _)
        (println "你所擁有的金幣:" _))
你所擁有的裝備: [短劍 火把 汽油]
你所擁有的銀幣: 2 ;這里金幣的值覆蓋了銀幣的值
你所擁有的金幣: 2
nil

在解構(gòu)多層嵌套時(shí)纳猪,更能發(fā)揮出它的威力

=> (def my-bag [["短劍" "火把" "汽油"] [512 2]])
=> (let [[[first-item _ third-item] [silver-coin gold-coin]] my-bag]
           (println "你背包里的第一件裝備:" first-item)
           (println "你背包里的第三件裝備:" third-item)
           (println "你所擁有的金幣:" gold-coin)
           (println "你所擁有的銀幣:" silver-coin))
你背包里的第一件裝備: 短劍
你背包里的第三件裝備: 汽油
你所擁有的金幣: 2
你所擁有的銀幣: 512
nil

我們像之前一樣對比一下原集合和解構(gòu)形式

my-bag [[  "短劍"     "火把"   "汽油"     ]  [    512           2    ] ]
       [[first-item     _     third-item]  [silver-coin   gold-coin] ]  my-bag

這也是我們?yōu)槭裁凑f,它可以清晰地表現(xiàn)原結(jié)構(gòu)的樣子


解構(gòu)的額外特性

  • 給剩余元素取名
=> (def my-bag [["短劍" "火把" "汽油"] 512 2])
=> (let [[[first-item _ third-item] & coins] my-bag]
        (println "你背包里的第一件裝備:" first-item)
        (println "你背包里的第三件裝備:" third-item)
        (println "你所擁有的錢幣:" coins))
你背包里的第一件裝備: 短劍
你背包里的第三件裝備: 汽油
你所擁有的錢幣: (512 2)
nil

使用 & 來把剩余元素作為一個(gè) list 綁定到一個(gè)名字
在做遞歸調(diào)用的時(shí)候這個(gè)功能用起來就太爽了

  • 給原集合取名

    使用 :as 來給你的原集合取名
    :as 是一個(gè) key (key 會(huì)在今后的介紹中出現(xiàn))

=> (let [[[first-item _ third-item] & coins :as my-bag-original] my-bag]
        (println "你背包里的第一件裝備:" first-item)
        (println "你背包里的第三件裝備:" third-item)
        (println "你所擁有的錢幣:" coins)
        (println "全部物品:" my-bag-original))
你背包里的第一件裝備: 短劍
你背包里的第三件裝備: 汽油
你所擁有的錢幣: (512 2)
全部物品: [[短劍 火把 汽油] 512 2]
nil

本文所介紹的解構(gòu)稱為順序解構(gòu)桃笙,它可以用來對順序集合做解構(gòu)氏堤,包括:

  • list,vector
  • 實(shí)現(xiàn)了 java.unit.List 接口的集合
  • Java 數(shù)組
  • 字符串
    對字符串的解構(gòu)結(jié)果是一個(gè)一個(gè)字符
=> (let [[f s t] "123"]  
     (print s))
2
nil

還有為 map 服務(wù)的解構(gòu)形式,在今后對 map 做單獨(dú)介紹時(shí)再詳細(xì)說明


  1. 可讀性鼠锈,指人類閱讀程序語言時(shí)的“舒適程度”闪檬,“易于理解程度”。讀起來更容易被理解的程序的可讀性就越高 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末购笆,一起剝皮案震驚了整個(gè)濱河市粗悯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌同欠,老刑警劉巖样傍,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異铺遂,居然都是意外死亡衫哥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門襟锐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撤逢,“玉大人,你說我怎么就攤上這事捌斧〉阎剩” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵捞蚂,是天一觀的道長妇押。 經(jīng)常有香客問我,道長姓迅,這世上最難降的妖魔是什么敲霍? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮丁存,結(jié)果婚禮上肩杈,老公的妹妹穿的比我還像新娘。我一直安慰自己解寝,他們只是感情好扩然,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著聋伦,像睡著了一般夫偶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上觉增,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天兵拢,我揣著相機(jī)與錄音,去河邊找鬼逾礁。 笑死说铃,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腻扇,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼债热,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了衙解?” 一聲冷哼從身側(cè)響起阳柔,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蚓峦,沒想到半個(gè)月后舌剂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡暑椰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年霍转,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片一汽。...
    茶點(diǎn)故事閱讀 39,703評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡避消,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出召夹,到底是詐尸還是另有隱情岩喷,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布监憎,位于F島的核電站纱意,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鲸阔。R本人自食惡果不足惜偷霉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望褐筛。 院中可真熱鬧类少,春花似錦、人聲如沸渔扎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晃痴。三九已至残吩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間愧旦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工定罢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留笤虫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像琼蚯,于是被迫代替她去往敵國和親酬凳。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評論 2 353

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