關於 Swift,我不喜歡的幾點

weixin_33976072發表於2017-02-10

作者:Soroush Khanlou,原文連結,原文日期:2016-08-14
譯者:Joy;校對:冬瓜;定稿:CMB

在以前,我已經寫過很多喜歡 Swift 的理由。但是今天,我想要寫的是這門語言不足的地方。這是一個錙銖必較的問題,所以我將舉例描述,去指出這門語言做的好的地方,做的不好的地方,以及其前景。

語言內定義 VS 非語言內定義

看一下 Ruby 語言的情況

Rubyattr_accessor 是一種為例項變數定義 settergetter 的方法。你可以像下面這樣使用它

class Person
    attr_accessor :first_name, :last_name
end

乍一看,它像是一種語言特性,與 Swiftletvar 屬性宣告方式相似。但是, 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的函式來為你傳遞的變數建立一個 gettersetter 方法。在 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語句。

Swiftfor..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 有時使用NilLiteralConvertibleSequenceTypeBooleanType來處理這些情況。這種 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

非同步/等待可以按照同樣的方式。定義一個 PromiseTask 協議,並遵守他們,那麼任務將是可以等待的(await)。thenflatMap 在這裡是可用的,根據使用者的需求,可以來選擇對應的語言特性。

超程式設計

我想要更多地去寫一些關於超程式設計的知識。我已經寫了關於 Objective-C 中的超程式設計,它和我們正在著手做的事情很相似。程式碼和元程式碼之間的界限是模糊的。Swift 編譯器中的程式碼是元程式碼,並且 Swift 本身也是程式碼。如果定義一個 operator函式的實現(就像你使用Ruby一樣)就是程式碼,那麼定義一個全新的運算子看起來就像是元程式碼。

作為一種面向協議的語言,Swift 很獨特,可以讓我們挖掘這門語言的語法魅力,就像我們用 BooleanTypeSequenceType所做的一樣。我很樂意去看一下這些被擴充套件的能力。

關鍵詞停止和語法開始或者語法停止和語法糖開始的界限,不是很明確,但是對於使用這門語言編寫程式碼的工程師,應該有能力去使用那些開發標準庫的工具。

本文由 SwiftGG 翻譯組翻譯,已經獲得作者翻譯授權,最新文章請訪問 http://swift.gg

相關文章