- 原文地址:Intro to Swift Functional Programming with Bob
- 原文作者:Bob Lee
- 譯文出自:掘金翻譯計劃
- 譯者:Deepmissea
- 校對者:thanksdanny,Germxu
Bob,函數語言程式設計是什麼鬼?
寫給年輕的自己的教程
老司機怎麼開車,我們就怎麼開
函數語言程式設計?
你懂的。很多人都討論它。你 Google 一下然後看了看前五篇文章,令人沮喪的是,你發現大部分文章只給出一個含糊不清的 Wikipedia 定義,像是:
“函數語言程式設計是一種程式設計正規化,能讓你的程式碼清晰又明確,沒有變數也沒有狀態。”
和你一樣,老兄,事實上,我也這樣搜尋過。我溫柔地做了個捂臉的表情,並輕聲迴應道:
這 TM 是啥?
先決條件
和閉包很像。如果你不理解什麼是後進和關鍵標識,比如 $0,那你還沒做好閱讀這篇教程的準備。你可以暫時離開,找這裡的資源來升升級。
非函數語言程式設計
我是十萬個為什麼。為什麼要學習函數語言程式設計?好吧,最好的答案往往來自於歷史。假設你要建立一個新增一個陣列的計算器應用。
// Somewhere in ViewController
let numbers = [1, 2, 3]
var sum = 0
for number in numbers {
sum += number
}複製程式碼
沒問題,但是如果我再新增一個呢?
// Somewhere in NextViewController
let newNumbers = [4, 5, 6]
var newSum = 0
for newNumber in numbers {
newSum += newNumber
}複製程式碼
這看起來就像我們重複我們自己,又長又無聊,還沒必要。你不得不建立一個 sum
來記錄新增的結果。這很讓人崩潰,五行程式碼。我們最好建立一個函式代替這些玩意。
func saveMeFromMadness(elements: [Int]) -> Int {
var sum = 0
for element in elements {
sum += element
}
return sum
}複製程式碼
這樣在需要使用 sum
的地方,直接呼叫
// Somewhere in ViewController
saveMeFromMadness(elements: [1, 2, 3])
// Somewhere in NextViewController
saveMeFromMadness(elements: [4, 5, 6])複製程式碼
停下別動,對。你現在已經嘗試了一個函式式正規化的使用。函式式就是用函式來得到你想要的結果。
比喻
在 Excel 或者 Google 的 Spreadsheet 上,如果要對一些值求和,你需要選擇表格,然後呼叫一個像是 C# 編寫的函式。
Excel 裡的求和函式
好,就是這樣,再見。感謝閱讀。 ?
宣告式 vs 命令式
最後,現在,是時候拿出詳細的函數語言程式設計定義了。
宣告式
我們經常把函數語言程式設計描述為宣告式的。你無須在意這個答案從何而來。舉個例子,一個人來爬珠穆朗瑪峰,可能從飛機上跳下去,也可能花好幾年從地下開始爬。你會得到同樣的結果。人們往往不知道 Excel 表格裡的 Sum
是怎麼組成的,但是,它就是得到相應的結果。
一個殘酷的例子,眾所周知的非函數語言程式設計,我們經常看到上面的呼叫被稱為命令式。它告訴你如何(how)得到從 A 到 B 的答案。
let numbers = [1, 2, 3]
var sum = 0
for number in numbers {
sum += number
}複製程式碼
人們知道你迴圈了 numbers
。但是,這有必要麼?我不在意它是怎麼做的,我只在意結果的出來的迅速快捷。
因此,Excel 和 Spreadsheet 融合了函數語言程式設計的正規化,來更快更簡單的獲取結果,而不需要關心具體的實現。(我父親也沒必要在處理公司財務資料的時候關心它)
其他的好處
在上面那個讓人崩潰的例子裡,我們不得不建立一個 var sum = 0
來跟蹤每個檢視控制器的增加結果。但是這有必要嗎?sum
的值不斷改變,如果我弄亂了總和怎麼辦?而且,我在10 條 tips 讓你成為一個更好的 Swift 開發者中提到過,
更多的變數 → 更多記憶 → 更多頭痛 → 更多 bug → 更多的生活問題。
更多的變數 → 容易 TM 的 → 完蛋
因此,函式式正規化確保在使用的時候不可變或者沒有狀態的變化。
而且,和你意識到的或即將發現的一樣,函式式正規化提供了一個更利於維護程式碼的模型。
目的
那好,現在你明白了為什麼我們喜歡函數語言程式設計。所以呢?嗯,這篇教程,只專注於基本面。我不會討論函數語言程式設計在事件和網路等等中的應用。我可能會發一些通過 RxSwift 來做這些事的教程。所以說如果你是新手,跟著我,螺旋穩。
(譯者配的圖 ? )
真正的工作
你可能已經見過像 filter
、map
、reduce
等等的一些東西。不錯,這些讓你用函式式的途徑中的過濾器來處理一個陣列。確保你對泛型的理解同樣的酷。
這全是關於基本面的東西。如果我能教你如何在泳池裡游泳,那你也可以在海里,湖裡,池塘裡,泥坑裡(最好不是)游泳,這這篇教程,如果你學會了基本面,你就可以建立你自己的 map
和 reduce
或者其他你想要的炫酷函式。你可以 google 東西,否則,你不會從我這裡得到這個叫“Bob”的開發者的解釋了。
過濾器
假設你有個陣列。
let recentGrade = ["A", "A", "A", "A", "B", "D"] // My College Grade複製程式碼
你想要過濾/帶來並且返回一個只包含 “A” 的陣列,這能讓我媽媽感到快樂。你怎麼用命令式的方式來做這個?
var happyGrade: [String] = []
for grade in recentGrade {
if grade == "A" {
happyGrade.append(grade)
} else {
print("Ma mama not happy")
}
}
print(happyGrade) // ["A", "A", "A", "A"]複製程式碼
這簡直讓人發瘋。我竟然寫了這種程式碼。我不會在校對的時候重新檢查,這很殘忍。檢視控制器中的8行程式碼??
不堪回首。
我們必須停止這種瘋狂,並拯救所有像你這麼做的人。讓我們建立一個函式來完成它。振作起來。我們現在要對付一下閉包了。讓我們試著建立一個過濾器來完成和上面一樣的工作。真正麻煩現在來了。
函式式的方式簡介
現在我們建立一個函式,有一個包含 String
型別的陣列並且有個閉包,型別是 (String) -> Bool
。最後,它返回一個過濾後的 String
陣列。為啥?忍我兩分鐘就告訴你。
func stringFilter(array: [String], returnBool: (String) -> Bool) -> [String] {}複製程式碼
你可能會對 returnBool
部分特別苦惱。我知道你在想什麼,
那麼,我們要在返回 Bool 下傳遞什麼?
你需要建立一個閉包,包含一個 if-else 語句來判斷陣列裡是否含有 “A”。如果有,返回 true
。
// A closure for returnBool
let mumHappy: (String) -> Bool = { grade in return grade == "A" }複製程式碼
如果你想讓他更短,
let mamHappy: (String) -> Bool = { $0 == "A" }
mamHappy("A") // return true
mamHappy("B") // return false複製程式碼
如果你對上面的兩個例子感到困惑,那你還適應不了這個副本。你需要鍛鍊一下然後再回來。你可以重讀我關於閉包的文章。連結
由於還沒完成我們 stringFilter
函式的實現,讓我們從離開的位置繼續。
func stringFilter (grade: [String], returnBool: (String) -> Bool)-> [String] {
var happyGrade: [String] = []
for letter in grade {
if returnBool(letter) {
happyGrade.append(letter)
}
}
return happyGrade
}複製程式碼
你的表情一定是 ?。我想說把刀放下,聽我解釋。通過 stringFilter
函式,你可以傳遞 mamHappy
作為 returnBool
。然後呼叫 returnBool(letter)
,把每個項傳遞個 mamHappy
,最終就是 mamHappy(letter)
。
它返回 true
或者 false
。如果返回真,把 letter
加到只有 “A” 的 happyGrade
裡。? 這就是為什麼我媽媽在過去 12 年裡感到開心的原因。
不管怎樣,最終執行一下函式。
let myGrade = ["A", "A", "A", "A", "B", "D"]
let lovelyGrade = stringFilter(grade: myGrade, returnBool: **mamHappy**)複製程式碼
直接輸入閉包
其實你不需要建立一個分離的 mamHappy
。可以直接在 returnBool
傳遞。
stringFilter(grade: myGrade, returnBool: { grade in
return grade == "A" })複製程式碼
我想讓它更簡潔。
stringFilter(grade: myGrade, returnBool: { $0 == “A” })複製程式碼
肉和土豆
祝賀,如果你已經到了這裡,那你已經做到了。我很感謝你的關注。現在讓我們建立一個野蠻點的,廣為人知的通用過濾器,你可以建立一堆你想要過濾的。比如,過濾你不喜歡的單詞,過濾陣列裡大於 60 小於 100 的數。過濾只包含真值的布林型別。
最棒的是它用一句話就可以形容。我們拯救了生命和時間。愛它,我們可以努力工作,但是我們要聰明的努力工作。
泛型程式碼
如果你對泛型程式碼感到不適,那你現在所在的位置並不正確,這裡車速很快,趕快到安全的地方,名字是“Bob,泛型是什麼鬼?”,然後帶點武器回來繼續。
我要建立一個含有 Bob
泛型的函式,你可以使用 T
或者 U
。但是你要知道,這是我的文章。
func myFilter<Bob>(array: [Bob], logic: (Bob) -> Bool) -> [Bob] {
var result: [Bob] = []
for element in array {
if logic(element) {
result.append(element)
}
}
return result
}複製程式碼
讓我們試著找點聰明的學生
應用到學校系統
let AStudent = myFilter(array: Array(1...100), logic: { $0 >= 93 && $0 <= 100 })
print(AStudent) // [93, 94, 95, ... 100]複製程式碼
應用到投票計數
let answer = [true, false, true, false, false, false, false]
let trueAnswer = myFilter(array: answer, logic: { $0 == true })
// Trailing Closure
let falseAnswer = myFilter(array: answer) { $0 == false }複製程式碼
Swift 裡的過濾器
幸運的是,我們不需要建立 myFilter
。Swift 已經為我們提供了一個預設的。現在我們建立一個從一到一百的陣列,然後只要小於 51 的偶數。
let zeroToHund = Array(1…100)
zeroToHund.filter{ $0 % 2 == 0 }.filter { $0 <= 50 })
// [2, 4, 6, 8, 10, 12, 14, ..., 50]複製程式碼
這就 OK 了。原始碼
我的訊息
我敢肯定你現在已經在想,怎麼在你的應用和程式裡使用函數語言程式設計。記住,你使用什麼語言都無所謂。
你需要清晰的是如何將函式式正規化引用到更多的領域。在你 Google 之前,我建議你花一點時間消耗一兩個腦細胞想一想。
從你理解 “filter” 背後的真正含義後,你現在可以更簡單的 google 然後檢視什麼是 map
和 reduce
,以及其他函式是怎麼組成的。我希望能你在不冷不熱的環境中學會游泳。
你現在只被你的想象力所限制。保持思考並 Google。
最後的話
在我個人看來,這篇文章是黃金。它出現在我被閉包和函式式的東西弄得一臉懵逼的時候。人們都喜歡特別簡單的原則。如果你喜歡我的解釋,請分享並推薦給更多的人。我收到的心越多,我就會越像抽水泵一樣,為每個人獻出更偉大的內容!而且,更多的心意味著基於 Medium 演算法上的更多觀點。
有 Instagram 上的 geek 嗎?我會發布我的一些日常並更新。歡迎大家隨時新增我,跟我打招呼!@bobthedev
Swift 會議
我的一個葡萄牙朋友 João 正在葡萄牙阿威羅組織一個 Swift 會議。不像許多人在的那裡,這次會議的目的是實驗性參與。觀眾與演講者可以相互交流 - 帶上你的膝上型電腦。這是我第一次的 Swift 會議。我超興奮!除此之外,它也是經濟實惠的。活動會在 2017 年的六月一號到二號舉行。如果你有興趣瞭解更多資訊,請隨時檢視網站這裡或下面的 Twitter。
SwiftAveiro (@SwiftAveiro) | Twitter
關於我
我在我的 Facebook 頁面上給出詳細的更新資訊。一般在美國東部時間的上午八點,我會發表文章。2017 年,我立志成長為 Medium 上 iOS Geek 社群中第一的 iOS 部落格。