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

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


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

綁定

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

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

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

這樣以來(lái)我們就可以更為方便的使用我們的 vector 了

=> (first my-items)
短劍

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

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

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

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


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

下面我們介紹的 let 函數(shù),它被稱為“局部綁定”
(在其它文章中也稱之為“本地綁定”、“本地值”、“l(fā)et 綁定”。指的都是我們這里介紹的內(nèi)容)
之所以叫它“局部綁定”,是因?yàn)槭褂盟鼇?lái)命名的名稱,只在 let 的括號(hào)范圍內(nèi)才能使用

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

我們?cè)诘诙偷谌齻€(gè)參數(shù)之間換行,這并不影響程序的執(zhí)行,無(wú)論你是否換行
但這增加了程序的可讀性[1]
養(yǎng)成良好的編程風(fēng)格,適當(dāng)?shù)目s進(jìn)和換行會(huì)增加程序的可讀性

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

  • 首先,它的第一個(gè)參數(shù)是一個(gè) vector (使用中括號(hào)包圍),中括號(hào)里元素個(gè)數(shù)必須為偶數(shù),
    因?yàn)橹欣ㄌ?hào)里每一對(duì)元素表示 “給值取的名字” - “值”(key-value),
    也就是說(shuō),我們可以一次性地給多個(gè)值取名字

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

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

我們來(lái)觀察更多的例子

=> (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 的括號(hào)外訪問(wèn) my-items
[128]
nil
CompilerException java.lang.RuntimeException: Unable to resolve symbol: my-coins in this context
  • 第一段代碼展示了如何使用 let 來(lái)一次性地定義兩個(gè)值 --- my-itemsmy-coins
    然后在 let 的括號(hào)范圍內(nèi),我們依次執(zhí)行了兩句表達(dá)式,而且我們?cè)诒磉_(dá)式之間使用換行以增加可讀性
    第一句表達(dá)式輸出 my-coins 的值
    第二句表達(dá)式直接使用 my-items,沒有使用括號(hào)包圍,這表示我們直接使用它的值,而不是把它作為函數(shù)
    由于 let 函數(shù)規(guī)定最后被執(zhí)行的表達(dá)式的值為 let 函數(shù)的返回值,所以此例中 let 函數(shù)的返回值為 my-items 的值
  • 而第二段代碼與第一段不同的是,它最后才執(zhí)行了 print 函數(shù)
    我們可以看到, print 函數(shù)的副作用效果出現(xiàn) --- my-coins 的值 [128] 被打印出來(lái),
    但是由于 print 函數(shù)的返回值始終為 nil
    所以此例中 let 函數(shù)的返回值為最后執(zhí)行的表達(dá)式的值 --- 即 print 函數(shù)的值,雖然在它之前我們對(duì) my-itemsmy-coins 的值進(jìn)行的訪問(wèn),但由于沒有執(zhí)行打印到屏幕上的操作,我們無(wú)法觀察到它們的值

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

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


解構(gòu)

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

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

或者在訪問(wèn)一個(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í)候,這種方式效率十分低下
不過(guò),當(dāng)你的嵌套層數(shù)非常多的時(shí)候,就該反思一下你的設(shè)計(jì)了
(可能寫出來(lái)的代碼你自己也讀不懂)

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

=> (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ì)長(zhǎng)成這樣:

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

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

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

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

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

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

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

而且,這個(gè)對(duì)應(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è)
看起來(lái)就像這樣

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

如果你只想要物品和金幣,你可以這樣來(lái)操作

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

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

=> (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è) _ 來(lái)忽視掉你不關(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

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

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

這也是我們?yōu)槭裁凑f(shuō),它可以清晰地表現(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

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

  • 給原集合取名

    使用 :as 來(lái)給你的原集合取名
    :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),它可以用來(lái)對(duì)順序集合做解構(gòu),包括:

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

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


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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容