Swift Syntactic Sugar 偏方可恥但有用
Syntactic sugar is syntax within a programming language that is designed to make things easier to read or to express
Swift在設計上,有許多的syntactic sugar,也就是簡化過方便使用的語法。syntactic sugar對於初學者來講,有時候是syntactic poison,因為通常各種syntactic sugar寫起來都不是很直觀,甚至還常會有符號摻雜在裡面,每個語言的syntactic sugar也都不盡相同,如果不事先了解,讀code起來就會非常痛苦。但這不代表syntax sugar就會讓程式碼可讀性下降,相反地,syntactic sugar有點像是講同樣語言的程式設計師之間的行話,你會講某個語言,但是卻不會行話,也還是會讓人家覺得你是外國人;但只要你能理解這個領域的行話,你就可以更快進入狀況,也能寫出讓大家更快理解的程式出來。
以下就介紹幾個Swift的syntactic sugar,以及一些實用pattern:
Ignoring symbol “_”
一個底線在Swift代表的是”忽略”,通常被使用在不需要的變數上面。假設我們想要印出一個dictionary裡面的key跟value,我們會寫成如下:
for (key, value) in aDic {
print("\(key)\(value)")
}
這樣可以簡單取用dictionary裡面的key跟value。但如果我們只需要key呢?
for (key, _ ) in aDic {
print("\(key)")
}
我們可以在原本value該出現的地方,改成只有一個”_”,那個參數位置還是會被unwrap,但是因為我們需要它,所以我們就把它丟到”_”這個黑洞裡,就不用再另外找個變數把它裝起來了。
這個語法設計的蠻巧妙的,不是非常麻煩的寫法,但卻可以解決以前ObjC回傳值語法上的模糊性,也可以讓一些不被使用的參數直接隱藏,減少干擾並增加可讀性。
(當然在上面這個case,有個更棒的寫法是直接針對aDic.keys做迴圈,不過解釋”_”的概念應該是足夠的)
Argument Labels
我們在定義一個function時,通常會這樣寫:
func ambulate( startCity: String, endCity: String) {
print("Tom walked from \(startCity) to \(endCity)")
}
在取用時,就會寫成這樣:
ambulate(startCity: "Taipei", endCity: "Tainan")`
但這樣的function呼叫,念起來不是很直觀,所以Swift提供了幫參數增加label的方式,寫法如下:
func walk(from startCity: String, to endCity: String) {
print("Tom walked from \(startCity) to \(endCity)")
}
在呼叫的時候,就可以寫成:
walk(from: "Taipei", to: "Tainan")
代表Tom從台北走到台南,是不是相當直觀?讓我們為Tom鼓鼓掌!
再來,如果我們把參數別名,設定成代表省略的”_”,如下:
func walk(_ startCity: String, _ endCity: String) {
print("Tom walked from \(startCity) to \(endCity)")
}
在取用函式的時候,就可以完全不用寫參數名稱:
walk("Taipei", "Tainan")
當然,這樣的寫法可讀性就不會太好,通常適用在不太會被重覆使用的function上,如果是比較泛用的function,就強烈建議要把參數名稱清楚地寫出來,以增加程式的可讀性。
Range Operator
這是一個基本語法,寫成如下:
a…b
中間是三個’.’,代表的是一個iterator從a到b(包含b),比方說 1…4 代表的就是 1, 2, 3, 4。
另外,從a到b但不包含b的寫法如下:
a..<b
這常見於for loop的使用,若我們想針對array的每個元素進行訪問,同時又想要拿到index,可以寫成如下:
for i in 0..<arr.count {
print("Index: \(i), element: \(arr[i])")
}
這樣既可以取index,也可以訪問原本array的元素。
PS. 當然,上面這種需求還有語意上更清楚的做法:
for (i, ele) in s.enumerated() {
print("Index: \(i), element: \(ele)")
}
用”enumerated()”這個array的method,可以針對array中的每個元素都回傳index與元素本身,這樣的寫法好處就是index跟element都清楚地列在iterator的前面,較先前的寫法更一目瞭然。當然一切都還是看寫code當時的需求而定,沒有絕對的對錯喔。
Nil Coalescing
在unwrap optional時,通常我們會使用這樣的語法:
var op: String?
/* do something */
if let x = op {
print(x)
}else {
print("")
}
其中op是一個optional的變數,在經過一些計算之後,op可能有值也有可能為空,所以我們會用if let語法來unwrap它成為一個區域變數,來取用有值的x,或是取用預設值空字串。這樣的寫法很明確但是稍微有點繁瑣。所以Swift提供了一個unwrap運算子”??”,寫法如下:
let x = op ?? ""
print(x)
這邊代表的是,我想要把op unwrap成為區域變數x,如果op是空的,那就給它一個預設值空字串,這樣寫比上面那樣簡潔很多,在預設值是固定時相當有用。要注意的是,如果這些變數都是屬於你的模組,那把預設值寫在init裡會是更好的選擇,但如果變數是第三方,或是一個ObjC的模組,這樣的pattern就會顯得相當有用。
Closure
Closure這個概念在現代語言中佔有非常重要地位,而在Swift中,有許多的syntactic sugar讓closure寫起來更簡潔、更能夠自我描述,以下就介紹幾個跟closure有關的syntactic sugar:
The Trailing Closures
這是一個神奇的特性,如果一個function的最後一個參數是closure,那你可以不用把這個closure包在”()”括號裡面,下面是一個以closure當參數的function:
func findingNemo( gotcha: () -> Void ){
gotcha()
}
一般呼叫方式:
findingNemo(gotcha: {
print("I got Nemo!")
})
但因為這個closure是最後一個參數,所以你可以不用把它包在”()”裡面,也不用指定參數名稱。效果一樣但是更簡潔了。
findingNemo {
print("I got Nemo!")
}
再讓我們看另外一個例子。
Swift針對array,提供了非常好用的map、reduce、filter等等function,以下是一個reduce的範例:
let s = [1, 2, 3, 4, 5]
s.reduce(0, { (result, e) -> Int in
return result + e
}) // Result is 1+2+3+4+5
reduce的用途是,針對array中的每一個元素,都做某一種操作,並且把運算結果存下來,讓下一個元素運用。上面這段程式,就是把0當初始值,並且依序把array中的數字都加起來。在邏輯上是經過這樣的運算:
- result = 0 + 1
- result = result + 2
- result = result + 3
- result = result + 4
- result = result + 5
最後result等於這個array的總和15。
這樣的函式,很常把後面的那個closure寫成簡單的樣式:
s.reduce(0) { (result, e) -> Int in
return result + e
}
然後其實回傳的型別是可以被省略的:
let a = [1, 2, 3, 4, 5]
a.reduce(0) { (result, e) in
return result + e
}
看起來.....好像還是跟原本差不多XD
這樣的pattern好處是最後一個closure常被定義為callback,把它視覺上跟前面的獨立出來,會比較簡潔且直觀。
好我知道不太有說服力,下面再介紹一個救援。
Shorthand Argument Names
在Swift裡,傳進closure的參數,可以被依序用一些醜醜的預設參數名來取用,如果一個closure有兩個參數,那在這個closure之中,就可以用**$0跟$1**來依序代表這兩個參數,所以上面的程式可以改寫成這樣:
s.reduce(0) {
return $0 + $1
}
就把輸入的參數整個拿掉了。如果一個closure裡面只有一行code,並且那一行的執行結果就是回傳值,那上面的code可以再進一步簡化成這樣:
'' s.reduce(0) { $0 + $1 }
讓我們再看看原本的code:
s.reduce(0, { (result, e) -> Int in
return result + e
})
是不是差超多!?
這也很好地說明了各種語言的syntactic sugar就是個兩面刃,如果你對這語言不熟,或是不管可讀性全部都用這種炫技式寫法,可能下場就是沒人看得懂你的code。相反地,如果你熟悉這些特性,並且適時地利用它,那它會讓程式既簡傑可讀性又大幅上升。
以上就是今天的Syntactic sugar介紹,雖然簡單但是初學時還是被許多特別的語法搞混了,所以整理了一下,希望對想學Swift的人有一點用處。有錯也歡迎指正喔!
PS. 最近認真學習日文,能看到這行表示你毅力驚人,可以分享一下怎樣堅持把一個語言學好的技巧嗎?
PS 10. 下一篇會寫一些比較完整的UI元件,歡迎支持
PS 11. 研究wordpress的各種設定中,有心得也歡迎分享,也有考慮過把文章擺在medium,但跟我原本medium的文章調性完全不合....