做者:Soroush Khanlou,原文連接,原文日期:2016-08-14
譯者:Joy;校對:冬瓜;定稿:CMBphp
在之前,我已經寫過不少喜歡 Swift 的理由。可是今天,我想要寫的是這門語言不足的地方。這是一個錙銖必較的問題,因此我將舉例描述,去指出這門語言作的好的地方,作的很差的地方,以及其前景。git
看一下 Ruby
語言的狀況github
Ruby
的 attr_accessor
是一種爲實例變量定義 setter
和 getter
的方法。你能夠像下面這樣使用它objective-c
class Person attr_accessor :first_name, :last_name end
乍一看,它像是一種語言特性,與 Swift
的 let
和 var
屬性聲明方式類似。可是, Ruby 的函數即使沒有括號也能夠被調起,並且這只是一個被定義在類範圍內的函數(在 Swift 中咱們將會調起一個靜態函數):編程
def self.attr_accessor(*names) names.each do |name| define_method(name) {instance_variable_get("@#{name}")} # 這是 getter 方法 define_method("#{name}=") {|arg| instance_variable_set("@#{name}", arg)} # This is the setter end end
若是你不能讀懂 Ruby
,沒有關係。它使用了一個名爲 define_method
的函數來爲你傳遞的變量建立一個 getter
和 setter
方法。在 Ruby
中,@first_name
意味着一個名爲 first_name
的實例變量。json
這是我愛上 Ruby
這門語言的緣由之一 ,他們首先設計了元數據工具集,去建立有用的語言特性,而後使用這些工具去實現他們想要的語言特性。 Yehuda Katz explores講述了 Ruby
是如何在它的 blocks
中實現這一想法的。由於 Ruby 的語言特性是經過相同的工具和相同的語言編寫而成,而且這門語言全部用戶都有權使用,因此,在這門語言的範疇內和類似風格的狀況下,用戶也能夠編寫語言特性。swift
Swift 的一個核心特性就是它的可選類型。它容許用戶定義某個變量是否能夠爲空。在系統中,被定義爲枚舉的格式:promise
enum Optional<WrappedType> { case Some(WrappedType) case None }
就像 attr_accessor
,這個特性使用了一個 Swift
的語言結構來定義自身。這很不錯,由於這意味着用戶可使用不一樣的語義來建立類似的事物,就像這個虛構的 RemoteLoading
類型:ruby
enum RemoteLoading<WrappedType> { case Loaded(WrappedType) case Pending }
它和 Optional
(可選類型)有相同的形態,但有着不一樣的含義。(在這篇博文中,Arkadiusz Holko 曾對這個枚舉有更進一步的闡述)數據結構
然而,在某種程度上,Swift
的編譯器知道 Optional
(可選) 類型但殊不知道 RemoteLoading
(遠程加載),它可讓你作一些特殊的事情。看一下這些相同的聲明:
let name: Optional<String> = .None let name: Optional<String> = nil let name: String? = nil var name: String?
讓咱們解析下它們的含義。第一條語句是完整的表述(帶有類型推斷)。你可使用相同的語法聲明你本身的 RemoteLoading
(遠程加載)屬性。第二條語句使用了 NilLiteralConvertible
協議來定義當你把這個值設置爲 nil
的時候所要執行的操做。雖然這種語法對於你本身的類型訪問是能夠的,可是使用 RemoteLoading
(遠程加載)卻顯得不是很正確。這是第一個語言特性,使得 C
族語言開發者對Swift有更舒服的感受,待會咱們會再次提到這一點。
第三條和第四條語句,編譯器開始使用 Optional
(可選) 類型來容許咱們編寫特殊的代碼。第三條語句使用了一個 Optional
(可選)類型的簡寫 T?。這被稱爲 語法糖,可讓你使用簡單的方式來編寫經常使用的代碼。最後一句是另一塊語法糖:若是你定義一個可選類型,可是你不賦給它任何值,那編譯器將會推測出它的值應該爲 .None
/ nil
(僅僅當他是一個 var
變量的時候才成立)。
後面的兩條語句不支持自定義類型。語言的 Optional 類型,能夠經過語言內存在的結構定義,以特定類型的異常結束,這個異常只有當前類型能夠訪問。
Swift
被定義爲「像在 C 語言家族中同樣」的語言,這個得益於循環和 For
語句。
Swift
的 for..in
語法結構是特殊的。任何遵照 SequenceType 的數據結構,均可以經過一個 for..in 循環來遍歷。這就意味着,我能夠定義本身的類型值,並聲明他們是序列化的,就能夠用for..in 循環來遍歷它了。
雖然 if 語句和 while 循環是經過 BooleanType 類型 在 Swift 2.2 中這樣子工做的 ,可是這種功能在 Swift 3 已經被移除了。我不能像在 for..in 循環語句中那樣子定義本身的布爾類型值而後在 if 語句中使用。
這裏有兩種基本的方法去定義語言特性,在 Swift 中都有體現到。第一種是建立一個能夠用來定義語言特性的元工具;另外一種是定義語言特性和語言類型值之間的一種明確和具體的關係。
你能夠會對符合 SequenceType 的類型值比符合 BooleanType 的類型值更加有用這個觀點提出異議。可是,Swift 3 已經徹底的移除了這個特性,因此,你只能認可:你不得不去認爲 BooleanType 是如此沒有用處以致於會被徹底禁止。
在 Swift 中的運算符也值得研究。語言中存在着定義運算符的語法,全部的算術運算符都是在這個語法中被定義的。用戶們能夠自由的定義本身的運算符,若是你想要建立本身的 BigInt 類型,同時也想要使用標準的算術運算符,這將是很是有用的。
然而 +
運算符在語言中被定義,三元運算符 ?:
卻沒有。當你點擊 +
時,命令跳轉到這個運算符的聲明處。當你點擊三元運算符中的 ?
和 :
的時候,卻沒有任何反應。若是你想要在你的代碼中使用單個的問號和感嘆號做爲操做符的話,這是作不到的。注意我這裏 不是 說在你的代碼中使用一個感嘆號操做符不是一個好主意。我只是想說,這個操做符已經被特殊對待,硬編碼到了編譯器,與其餘 C
中定義的操做符通常無二。
在這三個狀況中,我都比較了兩個東西:一是被標準庫用來實現特性的有用語法,一種是特權標準庫超越使用者代碼的特殊狀況。
最好的語法和語法糖是能夠被一門語言的做者利用本身的類型和系統不斷深刻挖掘的。Swift 有時使用NilLiteralConvertible
,SequenceType
和BooleanType
來處理這些狀況。這種 var name: String?
可以推測出本身的默認屬性值(.None)的方式很明顯不符合這個條件,所以這是一種不那麼給力的語法糖。
我認爲另外一個值得注意的點是,即便我愛 Ruby 的語法,可是 Ruby 在運算符和 falsiness
這兩個地方卻不是很靈活。你能夠自行定義已存在運算符的實現方式,可是不能添加一個新的運算符,並且運算符的優先級也是固定的。Swift 在這個方面更靈活。並且,固然,在 Swift 3 以前,Swift 在定義 falsiness
方面一樣具備更強的靈活性。
在某種程度上來講,Swift 的可選類型相似於 C 語言的可控性, Swift 的錯誤處理也相似於 C 語言的異常處理。Swift 的錯誤處理引入了一些新的關鍵詞: do
, try
, throw
, throws
, rethrows
, 和 catch
。
使用 throws
標記的函數和方法能夠 return
一個值或者 throw
一個 ErrorType
。被拋出的錯誤將會在 catch blocks函數中被捕捉到。在幕後,你能夠想象到 Swift 經過可能表明成功或者失敗的 _Result
類型(就像 antitypical/Result)重寫了函數的返回值類型,例如:
func doThing(with: Property) throws -> Value
重寫爲
func doThing(withProperty) -> _Result<Value, ErrorType>
事實上,這種 _Result 類型並無被顯式定義,而是 在編譯器中被隱式的處理了 )。這對於咱們的例子並無形成太多的不一樣。)在調用函數的內部,傳入成功的值的時候將會經過 try 語句,而發生錯誤的時候,則會跳入並執行 catch block
函數。
對比這個和以前的例子,例子中語言特性被定義在語言內部,再加上語法(例如操做符和 SequenceType)和語法糖(例如 Optional),那麼這個代碼就變的像咱們所期待的那樣了。相反的,Swift 的錯誤處理並無暴露它的內部 _Result 模型,因此用戶沒法使用或者改變它。
一些狀況下使用 Swift 模型來進行錯誤處理很是合適,例如 Brad Larson 用來移動機器人手臂的代碼和 個人 JSON 解析代碼 。其餘狀況的話,使用 Result 類型和 flatMap 會更合適。
其餘的代碼可能依賴異步處理,並想要傳遞一個 Result 的類型值給completion block
。蘋果的解決方案只能在某些特定的狀況下起到做用,給予在錯誤模型上更大的自由能夠幫助縮小這門語言和使用者之間的距離。Result 是很好的,由於它足夠靈活,能夠在上面玩不少花樣。 try / catch 語法並非很給力,由於它的使用十分嚴格並且只有一種使用方法。
Swift 4 承諾在不久後,將使用異步的語言特性。目前還不清楚將如何實現這些功能,可是 Chris Lattner 已經寫了不少關於 Swift 4的東西
一類併發:Actors、同步/等待、原子性、內存模型及其它一些相關主題
Swift 的異步的處理機制將會是什麼樣子的,異步/等待 是個人主要理論。在外行人看來,異步/等待 聲明何時函數是異步的
async Task<int> GetIntAsync() { return new Task<int>(() => { Thread.Sleep(10000); return 1 }); } async Task MyMethodAsync() { int result = await GetIntAsync(); Console.WriteLine(result); }
第一個函數方法, GetIntAsync
返回了一個任務,該任務等待一段時間後返回了一個值。由於這個函數返回了一個 Task
,因此被標記爲 async
。第二個函數方法,首先調用 MyMethodAsync
,使用關鍵詞 await
。這通知了整個系統,在完成 GetIntAsync
任務以前,這個系統能夠作其餘的事情。而一旦這個任務完成了,這個函數就會恢復控制功能,並在控制檯打印。
從這個例子看來,C#
的 Task
對象看起來很像 Promise 。此外,任何使用 await 的函數都必須被定義爲 async 。編譯器能夠確保這點。這個解決方案與 Swift 的錯誤處理模型很類似:被拋出的函數方法必須被捕捉到,而若是沒有,那這些函數方法必定也是被標記了 throws 。
它也像錯誤處理模型同樣有着缺陷。在加上新構造和一些關鍵詞以後,更像是語法糖,而不是一個有用的工具。這種構造一部分依賴於在標準庫中定義的類型,一部分依賴於編譯器定義的語法。
屬性行爲是 Swift 4 可能引入的另外一個重大特性。這裏是關於屬性行爲的拒絕提案,在 Swift 4中,這一個特性被更密切的關注。
屬性行爲讓你能夠對一個屬性附加上行爲,好比添加 lazy。這個 lazy 屬性,舉個例子,只有它在被第一次訪問時才設置值。但你如今已經可使用這個特定的行爲,這是直接硬編碼進 Swift 編譯器的。屬性行爲將使標準庫更容易地實現一些行爲,同時方便用戶去徹底自定義行爲。
可能這已是全世界最好的特性了。從一個已經被硬編碼進編譯器的一個特性開始,而後在這個特性取得必定聲望以後,建立一個更通用的框架來容許你經過語言自己定義這個特性。基於這一點,,任何 Swift 的開發者均可以實現相似的功能,精確調整來知足本身的需求。
若是 Swift 的錯誤模型遵循着相同的路徑,Swift 的標準庫可能會暴露出一個 Result 類型值,而後任何返回 Result 值的函數,均可以在必要的時候,使用 do
/ try
/ catch
語法(就像那些能夠單個失敗的並行、同步事件)。對於那些不須要符合當前可用語法的錯誤,就像異步錯誤,用戶將可使用一個共同的 Result
。這個 Result
須要不少鏈,用戶能夠 flatMap
。
異步/等待能夠按照一樣的方式。定義一個 Promise
或 Task
協議,並遵照他們,那麼任務將是能夠等待的(await)。then
和 flatMap
在這裏是可用的,根據用戶的需求,能夠來選擇對應的語言特性。
我想要更多地去寫一些關於元編程的知識。我已經寫了關於 Objective-C 中的元編程,它和咱們正在着手作的事情很類似。代碼和元代碼之間的界限是模糊的。Swift 編譯器中的代碼是元代碼,而且 Swift 自己也是代碼。若是定義一個 operator
函數的實現(就像你使用Ruby同樣)就是代碼,那麼定義一個全新的運算符看起來就像是元代碼。
做爲一種面向協議的語言,Swift 很獨特,可讓咱們挖掘這門語言的語法魅力,就像咱們用 BooleanType
和 SequenceType
所作的同樣。我很樂意去看一下這些被擴展的能力。
關鍵詞中止和語法開始或者語法中止和語法糖開始的界限,不是很明確,可是對於使用這門語言編寫代碼的工程師,應該有能力去使用那些開發標準庫的工具。
本文由 SwiftGG 翻譯組翻譯,已經得到做者翻譯受權,最新文章請訪問 http://swift.gg。