關于 Swift,我不喜歡的幾點
在以前,我已經寫過很多 喜歡 Swift 的理由 。但是今天,我想要寫的是這門語言不足的地方。這是一個錙銖必較的問題,所以我將舉例描述,去指出這門語言做的好的地方,做的不好的地方,以及其前景。
語言內定義 VS 非語言內定義
看一下 Ruby 語言的情況
Ruby 的 attr_accessor 是一種為實例變量定義 setter 和 getter 的方法。你可以像下面這樣使用它
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 的實例變量。
這是我愛上 Ruby 這門語言的原因之一 ,他們首先設計了元數據工具集,去創建有用的語言特性,然后使用這些工具去實現他們想要的語言特性。 Yehuda Katz explores 講述了 Ruby 是如何在它的 blocks 中實現這一想法的。因為 Ruby 的語言特性是通過相同的工具和相同的語言編寫而成,并且這門語言所有用戶都有權使用,所以,在這門語言的范疇內和相似風格的情況下,用戶也可以編寫語言特性。
可選類型
Swift 的一個核心特性就是它的可選類型。它允許用戶定義某個變量是否可以為空。在系統中,被定義為枚舉的格式:
enum Optional<WrappedType> {
case Some(WrappedType)
case None
}
就像 attr_accessor ,這個特性使用了一個 Swift 的語言結構來定義自身。這很不錯,因為這意味著用戶可以使用不同的語義來創建相似的事物,就像這個虛構的 RemoteLoading 類型:
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 所做的一樣。我很樂意去看一下這些被擴展的能力。
關鍵詞停止和語法開始或者語法停止和語法糖開始的界限,不是很明確,但是對于使用這門語言編寫代碼的工程師,應該有能力去使用那些開發標準庫的工具。
來自:http://swift.gg/2017/02/06/my-least-favorite-thing-about-swift/