我想說這真的是一篇很是很是好的文章,它經過對一個實例的API的優化,教會咱們如何寫出優美簡潔的Swift的函數式代碼。可是這個文章是視頻中做者的口述,因此翻譯過程當中不免有不當之處。你們能夠對着視頻和原文進行觀看和對比。程序員
原文地址編程
Swift第一次被公佈的一週後,我寫了一篇名爲「Swift不是函數式」的博文。兩年後,Swift仍然不是函數式。這篇文章並無對此進行闡述,而是將討論函數式語言幾十年來的經驗和研究,咱們能夠用本身快速的方式將這些經驗和研究帶入Swift。swift
它是單子、仿函數、haskell和優雅的代碼。api
函數式編程不是一種語言或語法,而是一種思考問題的方式。函數式編程在咱們有單子
以前已經存在了幾十年。函數式編程是一種思考如何分解問題,而後以結構化的方式將它們從新組合在一塊兒的方法。數組
讓咱們從swift中的一個簡單示例開始。app
var persons: [Person] = []
for name in names {
let person = Person(name: name)
if person.isValid {
persons.append(person)
}
}
複製代碼
這是一個簡單的循環,它作兩件事:將name
轉換成Person
,而後將這些人放入一個有效的數組中。很簡單,但這裏有不少事情。爲了理解發生了什麼,你須要在頭腦中執行這個。你須要思考,「這個數組是作什麼的?「函數式編程
它看起來很簡單,但可能會更簡單。咱們能夠把問題分開。咱們能夠把東西拆開。 咱們如今有兩個循環,每一個循環作的更少。每個都很簡單,更簡單的代碼讓咱們找到模式:建立一個數組,遍歷一些值,對每一個值執行一些操做,而後在最後將它們放到另外一個數組中。函數
當你有一些事情作了不少次,你可能應該提取函數-Swift有一個,它被稱爲map
。map
將某物的列表轉換爲其餘某物的列表。重要的是它說明了它的含義:可能persons
是names
到person
的映射。這是一份根據人名列出的名單。咱們沒必要在頭腦中執行任何東西,他就在代碼中表述咱們的意思。工具
let possiblePersons = names.map(Person.init)
let persons = possiblePersons.filter { $0.isValid }
複製代碼
另外一個循環也是一個很是常見的模式:它有一個名爲「filter」的方法。filter接受一個謂詞,它是一個返回bool
的函數。它使用函數給咱們返回有效的數據。咱們能夠把這些結合在一塊兒,從而得到possiblePersons
,而後把他們加入咱們的過濾器。post
從七行代碼到兩行代碼。另外,咱們能夠從新組合它們:這些都是咱們能夠從新組合起來的值。咱們用鏈式把他們進行組合。
它很容易閱讀,咱們能夠一行一行地閱讀。
let persons = names
.map(Person.init)
.filter { $0.isValid }
複製代碼
它十分易學而且很容易適應這種方式。在這樣的例子中你沒必要再寫一個for循環了。
1977年,John Backus(協助發明了Fortran 和 Algol)得到了圖靈獎,發表演講「編程能從馮諾依曼風格中解放出來嗎?「我喜歡這個標題。「馮·諾依曼風格」是指Fortran和Algol。
這篇論文是他爲發明它們而作的聲明。他表示命令式編程,一步一步地改變某種狀態,直到得到你想要的最終狀態。當他說「函數式」的時候,那並非指咱們今天所說的函數式,但他啓發了許多函數式研究人員去研究和學習。
這篇論文引發了個人興趣,咱們能夠回到swift:咱們如何將複雜的事情分解成簡單的事情。把這些簡單的東西泛化,而後用一些規則把它們粘在一塊兒,好比代數。
代數是一套規則,用來把事物組合在一塊兒,把它們拆開,而後轉換它們。咱們能夠想出能夠用來操縱程序的規則。咱們已經作到了:咱們作了一個循環,咱們把它分解成兩個簡單的循環,找到每個循環的通用形式,而後使用鏈式將它們從新組合在一塊兒。當haskell程序員第一次遇到swift時,他們每每會感到沮喪,由於試着作它們在Haskell語言中作的事。
在haskell中,與幾乎全部的函數式語言同樣,組成的基本單元是函數。有不少漂亮的方法來組合函數。我能夠經過將foldr
函數和+
函數粘合在一塊兒,並將其初始值設爲0,來建立一個名爲sum的新函數。無論你讀起來是否舒服,只要你這樣作了,它就至關漂亮了。
let sum = foldr (+) 0
sum [1..10]
複製代碼
你能夠用swift來作,可是它會很難看,並且它不能很好地工做,由於你在用錯誤單元組合它們。swift不是函數式。
swift中的組成單位是類型。類、結構、枚舉和協議,這些都是能夠組合的。咱們一般把兩種類型粘在一塊兒。經過實現一個函數並將它們粘在一塊兒,咱們能夠用更簡單的片斷來構建它們。
swift中另外一種很是常見的構圖是將類型放在語境中。你最經常使用的是optionals
。可選類型是根據語境肯定的,在語境中可能有值也可能沒有值。這是一個與類型相關的小信息:它是否存在?這就是語境的含義。添加語境比其餘跟蹤額外信息的方法強大得多。
extension MyStruct<T>: Sequence {
func makeIterator() -> AnyIterator<T> {
return ... }
}
複製代碼
咱們能夠跟蹤一個事實,即不存在整數,或者根本不存在值,這是由於咱們會從-1這樣的整數中竊取一個值,這意味着沒有值。可是如今你必須處處測試,這是醜陋的,容易出錯的,編譯器不能幫你。
若是咱們將整數改成可選的,這時編譯器能夠幫助您。你有這樣的語境「它存在嗎,它不存在嗎,我能夠幫助你確保你不會忘記這一點。」若是你曾經使用過-1,很容易忘記檢查,而後你的程序變得不可控了。
讓咱們創建一個更復雜的例子。
func login(username: String, password: String, completion: (String?, Error?) -> Void)
login(username: "rob", password: "s3cret") {
(token, error) in
if let token = token {
// success
} else if let error = error {
// failure }
}
複製代碼
這是一個很是常見的API。咱們有一個帶有username
和password
參數的login
函數,在某個時刻,它將返回一個token
和一個可能的錯誤。我認爲咱們能夠經過考慮語境作得更好。
第一個問題 是這個completion
。我說它是一個string
,是什麼樣的string
。她是一個token
.我能夠給它貼個標籤,但那沒用。在swift中,標籤不是類型的一部分。即便我這麼作了,仍是這個string
。字符串能夠是指不少東西。
Tokens
有規則:例如,它們可能必須是固定長度,或者不能爲空。這些都是能夠用字符串作的,但不能用tokens
。咱們但願有更多關於token
的上下文;它有規則,因此咱們但願捕獲這些規則。咱們能夠作到,咱們能夠給它更多的結構。這就是爲何它被稱爲struct
。
struct Token {
let string: String
}
複製代碼
我把字符串放入結構體中。這不會花費任何成本,也不會致使間接或任何額外的內存使用,但如今我能夠對此設置規則。
你只能用特定的字符串來構造它們。我能夠在上面加上一些擴展,這樣就沒有必要用任意的字符串了。這要好多了,我能夠對全部類型都這樣作;字符串固然沒問題,也能夠是字典、數組和整數類型。
當您擁有這些類型時,您能夠將它們提高到上下文中,並控制能夠放在它們上的內容。你能夠控制他表達的意思。我不須要標籤或註釋,由於第一個參數顯然是一個token
,由於它的類型是Token
。
第二個問題 是咱們要傳遞username
和password
。在大多數有這些的程序中,您老是將它們一塊兒傳遞;password
自己尤爲無用。我想建立一個規則,容許我用「and」組合用戶名和密碼,因此我須要一個「and」類型。咱們有一個,它又是一個結構。
struct Credential {
var username: String
var password: String
}
複製代碼
結構體是"and"類型。 Credential
是由username
和password
組成. 「and」類型被稱爲 「生產類型」. 我鼓勵你大聲說出來。例如:「憑證是用戶名和密碼。」這有意義嗎?若是它沒有意義,也許它是錯誤的類型,或者你建立它是錯誤的。
func login(credential: Credential, completion: (Token?, Error?) -> Void)
let credential = Credential(username: "rob",
password: "s3cret")
login(credential: credential) { (token, error) in
if let token = token {
// success
} else if let error = error {
// failure }
}
複製代碼
如今咱們能夠交換Credential
,而不是username
和password
:這也使得咱們的簽名更短更清晰。咱們還提供了許多不錯的可能性:在Credentials
上添加擴展,在其餘類型的規則下交換它們。也許咱們想要10次password
,或者access token
,或者facebook
或者google
等等。如今,我不須要更改代碼的任何其餘部分,由於我只是傳遞credentials
。
不過,它也有問題。咱們經過了元組(Token?, Error?) –元組是「and」類型。它們是匿名結構。咱們的意思是「也許是token,也許是error」?有四種可能性:二者都有,或者二者都沒有,或者一個或另外一個。只有兩種可能性是有意義的。若是我獲得了一個token
和error
?這是一個錯誤條件嗎?我須要一個致命的錯誤嗎?我須要忽略它嗎?你須要考慮一下這個問題,並可能針對它編寫測試。
問題是你不是說「也許」什麼的-你是指一個token
或一個error
。咱們有可使用的「或」類型嗎?
enum Result<Value> {
case success(Value)
case failure(Error)
}
複製代碼
It is an enum - enums are 「or」 types (this or that), whereas structs are 「and」 types (this and that). Like 「and」 types are called 「product types」, 「or」 types are called 「sum types」. 這是一個枚舉 - enums
是 「or」類型,就像結構體是「and」類型。正如「and」類型被稱做「生產類型」,「or」類型被稱爲「和類型」
func login(credential: Credential, completion: (Result<Token>) -> Void)
login(credential: credential) { result in
switch result {
case .success(let token): // success
case .failure(let error): // failure
}
}
複製代碼
我想創建這個result
類型。這讓我很困擾,由於它不是內置在swift中的。它很容易建造。咱們將提高咱們的值,給它更多的語境。它從一種值轉變爲一個成功的值。
咱們的錯誤變成一個失敗的錯誤,咱們有更多的語境。若是咱們將result
、生成的token
扔進咱們的api中,那麼咱們必須針對全部這些狀況編寫測試來保證全部不可能的狀況都會消失。咱們沒必要擔憂他們,由於他們是不可能的。我但願錯誤不可能,而不是編寫測試用例。
我喜歡這個API。我用credential
登陸,它會給我一個生成的token
。
這是函數式編程的真正精髓,也是咱們應該帶給swift的:複雜的事情能夠分解成更小、更簡單的事情。
咱們能夠爲這些簡單的事情找到通用的解決方案,咱們可使用一致的規則將這些簡單的事情從新組合起來,讓咱們對咱們的程序進行推理。這使得編譯器更容易查出bug,我認爲70年代的John Backus
徹底贊成這一點。把它拆了,把它造起來。