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-items
和my-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-items
和my-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ù)里
- 把原來 “給值取的名字” 的位置唇牧,寫成一個(gè) vector 的形式(即用中括號包圍)
- 原來填寫 “值” 的位置罕扎,寫上你要解構(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ì)說明
-
可讀性鼠锈,指人類閱讀程序語言時(shí)的“舒適程度”闪檬,“易于理解程度”。讀起來更容易被理解的程序的可讀性就越高 ?