<b>本文講的是詳解Swift的類型檢查器,</b>
這篇文章将圍繞曾不斷使我重寫代碼的一些 Swift 編譯器的報錯資訊展開:
錯誤:你的表達式太過于複雜,請将其分解為一些更為簡單的表達式。(譯者注:原文是<code>error: expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions</code>)
<a></a>
我會看那個觸發錯誤的例子,談談以後由相同底層問題引起以外的編譯錯誤的負面影響。我将會帶領你看看在編譯過程中發生了什麼,然後告訴你,怎樣在短時間内去解決這些報錯。
我将為編譯器設計一種時間複雜度為線性算法來代替原本的指數算法來徹底的解決這個問題,而不需要采用其餘更複雜的方法。
如果你嘗試在 Swift 3 中編譯這段代碼,那麼将會産生報錯資訊:
1
let a: Double = -(1 + 2) + -(3 + 4) + 5
這段代碼無論從哪方面來講都是合法且正确的代碼,從理論上講,在編譯過程中,這段代碼将會被優化成一個固定的值。
但是這段代碼在編譯過程中沒有辦法通過 Swift 的類型檢查。編譯器會告訴你這段代碼太複雜了。但是,等等,這段代碼看起來一點都不複雜不是麼。裡面包含 5 個變量, 4 次加法操作, 2 次取負值操作和一次強制轉換為 <code>Double</code> 類型的操作。
但是,編譯器你怎麼能說這段僅包含 12 個元素的語句相當複雜呢?
這裡有非常多的表達式在編譯的時候會出現同樣的問題。大多數表達式包含一些變量,基礎的資料操作,可能還有一些重載之類的操作。接下來的表達式在編譯時會面對同樣的錯誤資訊:
2
3
4
5
6
7
8
9
10
11
let b = String(1) + String(2) + String(3) + String(4)
let c = 1 * sqrt(2.0) * 3 * 4 * 5 * 6 * 7
let d = ["1" + "2"].reduce("3") { "4" + String($0) + String($1) }
let e: [(Double) -> String] = [
{ v in String(v + v) + "1" },
{ v in String(-v) } + "2",
{ v in String(Int(v)) + "3" }
]
上面的代碼都是符合 Swift 文法及程式設計規則的,但是在編譯過程中,它們都沒有辦法通過類型檢查。
編譯報錯隻是 Swift 類型檢查器缺陷帶來的副作用之一,比如,你可以試試下面這個例子:
let x = { String("\($0)" + "") + String("\($0)" + "") }(0)
這段代碼編譯時不會報錯,但是在我的電腦上,使用 Swift 2.3 将花費 4s 的時間,如果是使用 Swift 3 将會花費 15s 時間。編譯過程中,将會花費大量的時間在類型檢查上。
現在,你可能不會遇到太多需要耗費這麼多時間的問題,但是一個大型的 Swift 項目中,你将會遇到很多<code>expression was too complex to be solved in reasonable time</code> 這樣的報錯資訊。
接下來,我将講一點 Swift 類型檢查器的特性:類型檢查器選擇盡可能的解決非泛型重載的問題。 編譯器中處理這種特定行為的路徑下的代碼注釋對此給出了解釋,這是一種避免性能問題的優化手段,用于優化造成 <code>expression was too complex</code> 報錯的性能問題。
接下來是一些具體的例子:
let x = -(1)
這段代碼将會編譯失敗,我們會得到一個 <code>Ambiguous use of operator ‘-‘</code> 的報錯資訊。
這段代碼并不算很模糊,編譯器将會明白我們想要使用一個整數類型的變量,它将會把 <code>1</code> 作為一個<code>Int</code> 進行處理,同時從标準庫中選擇如下的重載方式:
prefix public func -<T : SignedNumber>(x: T) -> T
然而,Swift 隻能進行非泛型重載。在這個例子中,<code>Float</code> 、 <code>Double</code> 、 <code>Float80</code> 類型的實作并不完善,編譯器無法根據上下文選擇使用哪種實作,進而導緻了這個報錯資訊。
某些特定的優化可以對操作符進行優化,但是可能導緻如下的一些問題:
func f(_ x: Float) -> Float { return x }
func f<I: Integer>(_ x: I) -> I { return x }
let x = f(1)
prefix operator %% {}
prefix func %%(_ x: Float) -> Float { return x }
prefix func %%<I: Integer>(_ x: I) -> I { return x }
let y = %%1
在這段代碼裡,我們定義了兩個函數( <code>f</code> 和一個自定的操作 <code>prefix %%</code> )。每個函數都進行了兩次重載,一個參數為 <code>(Float) -> Float</code> ,另一個是 <code><I: Integer>(I) -> I</code>。
當調用 <code>f(1)</code> 的時候,将會選擇使用 <code><I: Integer>(I) -> If(1)</code> 的實作,然後 <code>x</code> 将會作為 <code>Int</code> 類型進行處理。這應該是你所期待的方式。
當調用 <code>%%1</code> 時,将會使用 <code>(Float) -> Float</code> 的實作,同時會将 <code>y</code> 作為 <code>Float</code> 類型處理,這和我們所期望的恰恰相反。在編譯過程中,編譯器選擇将 <code>1</code> 作為 <code>Float</code> 處理,而不是作為 <code>Int</code> 處理,雖然作為 <code>Int</code> 處理也同樣能正常工作。造成這樣情況的原因是,編譯器在對方法的進行泛型重載之前就已經先行确定變量的類型。這不是基于前後文一緻性的做法,這是編譯器對于避免類似于 <code>expression was too complex to be solved</code> 等報錯資訊以及性能優化上的一種妥協。
通常來講,Swift 裡的顯示代碼太過複雜的缺陷并不是一個太大的問題,當然前提是你不會在單個表達式裡使用兩個或兩個以上的下面列出的特性:
方法重載(包括操作符重載)
常量
不明确類型的閉包
會引導 Swift 進行錯誤類型轉換的表達式
一般而言,如果你不使用如上面所述的特性,那麼你一般不會遇到類似于 <code>expression was too complex</code> 的報錯資訊。然而,如果選擇是用了上面所訴的特性,那麼你可能會面臨一些讓你感到困惑的問題。通常,在編寫一個足夠大小的方法和其餘正常代碼的時候,将會很容易用到上面這些特性,這意味着有些時候我們可能要仔細考慮怎樣避免大量使用上面這些特性。
你肯定是想隻通過一點細微的修改來通過編譯,而不是大量修改你的代碼。接下來的一點小建議可能幫得上一些忙。
當上面所訴的編譯報錯資訊出現時,編譯器可能建議你将原表達式分割成不同的子表達式:
let x_1: Double = -(1 + 2)
let x_2: Double = -(3 + 4)
let x: Double = x_1 + x_2 + 5
好了,從結果上來看,這樣的修改是有效的,但是卻讓人有點蛋疼,特别是在分解成子表達式的時候會明顯破壞代碼可讀性的時候。
另一個建議是通過顯示類型轉換,減少編譯器在編譯過程中對方法和操作符重載的選取次數。
let x: Double = -(1 + 2) as Double + -(3 + 4) as Double + 5
上面這種做法避免了在使用 <code>(Float) -> Float</code> 或者是 <code>(Float80) -> Float80</code> 編譯器需要去查找相對應的負号重載。這樣的做法很有效的将編譯過程中編譯器的6次查找相對應的方法重載過程降至4次。
在上面的處理方式中有一個點要注意一下:不同于其餘語言,在 Swift 中 <code>Double(x)</code> 并不等同于 <code>x as Double</code>。構造函數通常會如同普通方法一樣,當有不同參數的重載需求時,編譯器還是會将構造函數的各種重載加入到搜尋空間中(盡管這些重載可能在代碼中的不同的位置)。在前面所舉的例子裡,通過在括号前用 <code>Double</code> 進行顯示類型轉換會解決一部分問題(這種方法有利于編譯器進行類型檢查),同時在一些情況下,采用這種方法會導緻出現一些其餘的問題(請參見本文開始所舉的關于 <code>String</code> 的例子)。最終, 使用<code>as</code> 操作符是在不增加複雜度的情況下解決這類的問題的最好方式。幸運的是,<code>as</code> 操作符的優先級比大多數二進制運算符更高,這樣我們可以在大多數的情況下使用它。
另一種方法是使用一個獨立命名的自定義函數:
let x: Double = myCustomDoubleNegation(1 + 2) + myCustomDoubleNegation(3 + 4) + 5
這種方法可以解決之前方法重載所帶來的一系列問題。然而,在一系列輕量級的代碼裡使用這種方式會讓我們的代碼顯得格外的醜陋。
好了,讓我們來說說最後的方法,在很多情況下,你可以根據情況自行替換方法和操作符:
let x: Double = (1 + 2).negated() + (3 + 4).negated() + 5
因為在使用對應方法時,和使用常見算數運算符相比,會有效的減少重載次數,同時使用<code>.</code> 操作符時其效率相較于直接調用方法更高,是以,這種方法能有效解決我們前面所提到的問題。
編譯時出現的 <code>expression was too complex</code> 錯誤是由 Swift 編譯器的語義分析系統所抛出的。語義分析系統的意義在于解決整個代碼裡的類型問題,進而確定輸入表達式的類型是正确且安全的。
講真,上面的東西可能太抽象了,讓我們看點具體的例子吧。
let a = 1 + 2
類型限制系統将表達式解析成下面這個樣子:
每個節點的名字都以 <code>T</code> 開頭(意味着需要待确定明确的類型),然後它們用來代表需要解決的類型限制或者方法重載。在這個圖裡,這些節點被如下的規則所限制:
<code>T1</code> 是 <code>ExpressibleByIntegerLiteral</code> 類型
<code>T2</code> 是 <code>ExpressibleByIntegerLiteral</code> 類型
<code>T0</code> 是一個傳入 <code>(T1,T2)</code> 傳回 <code>T3</code> 的方法
<code>T0</code> 是 <code>infix +</code> ,其在 Swift 裡有28種實作
<code>T3</code> 與 <code>T4</code> 之間可以進行交換
小貼士: 在 Swift 2.X 中,<code>ExpressibleByIntegerLiteral</code> 的替代者是<code>IntegerLiteralConvertible</code>
在這個系統中,類型限制系統遵循着 最小分離 原則。分割出來的單元被這樣一個規則所限制着,即,每個單元都是一個擁有一套獨立值的個體。在上面的這個例子裡,實際上隻有一個最小單元:在上述的限制 4 裡,<code>T0</code> 發生了重載。在重載之時,編譯器選擇了 <code>infix +</code> 實作清單裡第一種實作:即簽名是 <code>(Int, Int) -> Int</code> 的實作。
通過上述這個最小的單元,類型限制系統開始對元素進行類型限制:根據限制 3 <code>T1</code>、 <code>T2</code>、 <code>T3</code> 被确定為 <code>Int</code> 類型,根據限制 4 , <code>T4</code> 同樣被确認為 <code>Int</code> 類型。
在 <code>T1</code> 、 <code>T2</code> 被确定為 <code>Int</code> 之後(最開始它們被認為是<code>ExpressibleByIntegerLiteral</code>), <code>infix +</code> 的重載方式便已經确定,這個時候編譯器便不需要再考慮其餘可行性,并把其當做最終的解決方案。我們在确定每個節點對應的類型後,我們便可以選擇我們所需要的重載方法了。
到目前為止,并沒有什麼超出我們意料之中的異常出現,你可能想象不到當表達式開始變得複雜之時, Swift 的編譯系統将會開始不斷的出現錯誤資訊。來讓我們修改下上面的例子:第一·将 <code>2</code> 放在括号裡,第二·添加負号操作符,第三·規定傳回值為 <code>Double</code> 類型。
let a: Double = 1 + -(2)
整個節點結構如下圖所述:
節點限制如下:
<code>T3</code> 是 <code>ExpressibleByIntegerLiteral</code> 類型
<code>T2</code> 是一個傳入 <code>T3</code> 傳回 <code>T4</code> 的方法
<code>T2</code> 是 <code>prefix -</code>,其在 Swift 裡有6種實作
<code>T0</code> 是一個傳入 <code>T1</code>、<code>T4</code>,傳回 <code>T5</code> 的方法
<code>T5</code> 是 <code>Double</code> 類型
相較于上面的例子,這裡多了兩個限制,讓我們看看類型限制系統會怎樣處理這個例子。
第一步:選擇最小分離單元。這次是限制 4 :“ <code>T2</code> 是 <code>prefix -</code>,在 Swift 裡有6種實作”。最後系統選擇了簽名為 <code>(Float) -> Float</code> 的實作。
第二步:和第一步一樣,選擇最小分離單元,這次是限制 6 :“<code>T0</code> 是 <code>infix +</code> ,其在Swift 裡有28種實作”。系統選擇了簽名為 <code>(Int, Int) -> Int</code> 的實作。
最後一步是:利用上述的類型限制确定所有節點的類型。
然而,這裡出現了點問題:在第一步裡我們選擇的簽名為 <code>(Float) -> Float</code> 的 <code>prefix -</code> 實作和第二步裡我們選擇的簽名為 <code>(Int, Int) -> Int</code> 的 <code>infix +</code> 實作和我們的限制 5 (<code>T0</code> 是一個傳入 <code>T1</code>、<code>T4</code>,傳回 <code>T5</code> 的方法)發生了沖突。解決方法是放棄目前的選擇,然後重新復原至第二步,為 <code>T0</code>
最終,系統将周遊所有的 <code>infix +</code> 實作,然後發現沒有一種實作同時滿足限制 5 和限制 7 (<code>T5</code> 是 <code>Double</code> 類型)。
是以,類型限制系統将復原至第一步,為 <code>T2</code> 選取了簽名為 <code>(Double, Double) -> Double</code> 的實作。最後,這種實作也滿足了 <code>T0</code> 的限制。
然而,在發現 <code>Double</code> 類型和 <code>ExpressibleByIntegerLiteral</code> 互相不比對後,類型限制系統将繼續復原,尋找合适的重載方法。
<code>T2</code> 總共有6種實作,但是最後3種實作不能被優化(因為它們是通用的實作,是以優先級高于顯示聲明參數為 <code>Double</code> 的實作)。
拜這種特殊的“優化”所賜,類型限制系統需要76次查詢才能找到一個合理的解決方案。如果我們添加了其餘的一些新的重載,那麼這個數字會變得超出我們的想象。例如,我們我們在例子裡添加另外一個 <code>infix +</code> 操作符,比如: <code>let a: Double = 0 + 1 + -(2)</code> ,那麼将需要1190次查詢才能找到合理的解決方案。
根據我的測試,單語句内擁有6個分離單元,便足以觸發 Swift 中的 <code>expression was too complex</code> 的錯誤。
針對本文所反複提到的這個問題,最好的解決方法就是在編譯器中進行修複。
為了避免生成 n 維笛卡爾乘積空間,我們需要設計一個方法來實作相關邏輯實作的獨立性,而不讓它們彼此依賴。
在開始之前我必須給你們一個很重要的提醒:
友情提醒,這些東西僅代表我的個人觀點:接下來的一些讨論,都是我從理論的角度上來分析怎樣在 Swift 的類型限制系統中怎樣去解決函數重載的問題。我并沒有寫一些東西來證明我提出的解決方案,這可能意味着我會忽略某些非常重要的東西。
我們想實作如下兩個目标:
限制一個節點不應該與其餘節點互相依賴或引用
從前一個方法分析出來的分離單元應該與後一個方法分離出來的存在着交集,并進一步簡化分離單元的兩個限制條件。
第一個目标,可以通過限制節點的限制路徑實作。在 Swift 中,每個節點的限制是雙向的,每個節點的限制都從表達式的每一個分支開始,然後依照着周遊主幹->線性周遊子節點的方式不斷傳播。在這個過程中,我們可以有選擇性的簡單地合并相同的限制邏輯來組合這些限制,而不是從其餘節點引用相對應的類型限制。
第二個目标裡,支援前面通過減少類型限制的傳播複雜度來進一步簡化相關限制條件。每個重載方法的分離單元之間最重要的交叉點是一個重載函數的輸出,可能會作為另一個重載函數的輸入。這個操作應該根據參數互相交叉的兩個重載方法所産生的2維笛卡爾積來進行計算。對于其餘的可能存在的交叉點來說,給出一個真正意義上的數學上的嚴格交叉證明是非常困難的,同時這樣的證明是沒有必要的,我們隻需要複制 Swift 裡在複雜情況下的對于類型選擇的時所采用的貪婪政策即可。
讓我們看看如果我們實作了前文所講的兩個目标後,類型限制系統将會變成什麼樣子。首先讓我們複習下之前所生成的節點圖:
然後讓我們也複習下以下節點限制:
我們從右至左進行周遊(從葉子節點向主幹周遊)。
在節點限制從 <code>T3</code> 向 <code>T2</code> 傳播時,添加了這樣一個新的限制:“ <code>T2</code> 節點的輸入值必須是一個由 <code>ExpressibleByIntegerLiteral</code> 轉化而來的值”。現在在新的限制規則和原有規則同時發生作用後,一旦我們确認所有擁有 <code>T2</code> 的節點都被新規則限制成功之後,或者是與“特定操作重載優先于通用操作重載(比如在 <code>prefix -</code> 中 <code>Double</code>、 <code>Float</code> 或者是<code>Float80</code> 會被優先重載)”這條規則沖突之時,便可以丢棄我們建立立的節點限制規則。在節點限制從 <code>T2</code> 向 <code>T4</code> 中傳播的過程中,添加新限制為:“ <code>T4</code> 必須是 <code>prefix -</code> 所傳回的6中類型的值之一,其中 <code>Double</code>、<code>Float</code> 或 <code>Float80</code> 優先被考慮)。在節點限制從<code>T4</code> 朝 <code>T0</code> 傳播的過程中,添加新限制為:“ <code>T0</code> 的第二個參數必須是從 <code>prefix -</code> 傳回的6種參數裡的任意一種演變而來,其中 <code>Double</code>、 <code>Float</code> 或 <code>Float80</code> 類型優先)。在結合 <code>T0</code> 已有的節點限制後,<code>T0</code> 的節點限制變為:“ <code>T0</code> 是 <code>infix +</code> 的6種實作之一,同時從右側傳入的參數是來自 <code>prefix -</code> 傳回參數中的任意一種,在這個過程中類型是<code>Double</code>、 <code>Float</code> 或者 <code>Float80</code> 的參數優先被考慮)。在節點限制從 <code>T1</code> 朝 <code>T0</code> 傳遞之時,沒有新的限制條件需要添加(在這裡,<code>T0</code> 已經被我們所增加的限制條件嚴格限制了,同時,原本所使用的 <code>ExpressibleByIntegerLiteral</code> 類型已經被 <code>Double</code>、 <code>Float</code>或者 <code>Float80</code> 中的任意一種類型所替代了)。在節點限制從 <code>T0</code> 向 <code>T5</code> 傳播時,需新增加限制為:“ <code>T5</code> 是 <code>infix +</code> 的6種傳回值中的一種,且 <code>infix +</code> 的第二個參數是來自<code>prefix -</code> 的傳回值,在這個過程中,<code>Double</code>、 <code>Float</code> 或者 <code>Float80</code> 類型優先被考慮)。在上述限制的共同作用下,我們可以最終确認 <code>T5</code> 的類型為 <code>Double</code>。
經過上述過程的變動之後,整個節點限制集疊代成下面這個樣子:
<code>T2</code> 是 <code>prefix -</code> 的6種實作之一,同時為了滿足在 Swift 中特殊操作重載優先級高于通用運算重載的原則,類型為 <code>Double</code>、 <code>Float</code> 或者 <code>Float80</code> 的 <code>prefix -</code>重載優先被考慮。
<code>T4</code> 是 <code>prefix -</code> 的六種傳回值之一,同樣為了滿足在 Swift 中特殊操作重載優先級高于通用運算重載的原則,類型為 <code>Double</code>、 <code>Float</code> 或者 <code>Float80</code> 的 <code>prefix -</code>重載優先被考慮。
<code>T0</code> 是 <code>infix +</code> 的6種實作之一,同時從右側傳入的參數是來自 <code>prefix -</code> 傳回參數中的任意一種,在這個過程中為了滿足在 Swift 中特殊操作重載優先級高于通用運算重載的原則,類型是 <code>Double</code>、 <code>Float</code> 或者 <code>Float80</code> 的參數優先被考慮
現在我們開始從左至右進行周遊(先周遊主幹,後周遊葉子節點)。
首先從 <code>T5</code> 開始周遊,限制 5 是:“ <code>T5</code> 是 <code>Double</code> 類型的節點”。這時我們為 <code>T0</code> 添加新的限制:“ <code>T0</code> 的傳回值類型一定要是 <code>Double</code> 類型的”。在這個限制生效後,我們就可以排除除 <code>(Double, Double) -> Double</code> 之外的 <code>infix +</code> 的重載了。節點限制繼續從<code>T0</code> 朝 <code>T1</code> 傳遞,根據 <code>infix +</code> 的<code>(Double, Double) -> Double</code> 重載的參數要求,我們為 <code>T1</code> 建立一個新的限制: <code>T1</code> 一定是 <code>Double</code> 類型的。在多種限制的作用下,之前所提到的“<code>T1</code> 是 <code>ExpressibleByIntegerLiteral</code> 類型”變為“<code>T1</code> 是 <code>Double</code> 類型”。在節點限制從 <code>T0</code> 朝 <code>T4</code> ,根據 <code>infix +</code> 的第二個參數的要求,我們确定 <code>T4</code> 的類型為<code>Double</code>。節點限制從 <code>T4</code> 朝 <code>T2</code> 傳播的過程中,我們新增加一個限制:“ <code>T2</code> 的傳回值一定為 <code>Double</code> 類型”。在以上規則共同作用下,我們可以确定 <code>T2</code> 為 <code>prefix -</code> 的參數類型為 <code>(Double) -> Double</code> 重載。最後根據以上的限制,我們可以得知 ‘T3’ 的類型為 ‘Double’。
最後整個類型限制系統程式設計下面這個樣子:
<code>T1</code> 為 <code>Double</code> 類型。
<code>T3</code> 為 <code>Double</code> 類型。`
<code>T2</code> 是 <code>prefix -</code> 的參數為 <code>(Double) -> Double</code> 類型的重載
<code>T0</code> 是 <code>infix +</code> 的參數為 <code>(Double, Double) -> Double</code> 類型的重載
<code>T5</code> 為 <code>Double</code> 類型。
好了,現在整個類型限制操作便已經告一段落了。
唔,我提出這算法的目的是改善方法重載的相關操作,是以,我将方法重載的次數用 n 表示。然後我将平均每個函數重載次數用 m 表示。
如我前面所述,在 Swift 中,編譯器是通過在一個 n 維的笛卡爾積空間内進行搜尋來确定最終的結果。它的時間複雜度是 O(m^n) 。
而我所提出的算法,是在一個2維的空間内去搜尋 n-1 個分離單元來實作的。其執行時間是m^2*n.因為 m 是和 n 相關聯的,我們可以得到其最終的時間複雜度為 O(n) 。
通常來講,在 n 為很大的時候,線性複雜度的算法比指數時間複雜度的算法更能适應目前的狀況,不過我們得搞清楚什麼樣的情況才能被稱之為 n 為很大的數。在這個例子中,3 已經是一個非常 “大” 的數了。正如我前面所提到的一樣,Swift 自帶的類型限制系統将進行1190次搜尋來确認最後的結果。而我設計的算法隻需要336次搜尋。這可以說很明顯的降低了最後的耗時。
我做了一個很有趣的實驗:在之前所提到的 <code>let a: Double = 1 + -(2)</code> 這個例子裡,不管是 Swift 裡的類型限制系統,還是我所設計的算法,它們都是在一個2維的笛卡爾積空間内進行搜尋,裡面都包含了168中可能性。
Swift 裡現在所采用的類型限制算法選取了在 <code>prefix -</code> 和 <code>infix +</code> 重載生成的2維笛卡爾積空間内的168種可能性的76種。但是這樣做的話,整個過程裡會産生567次對<code>ConstraintSystem::matchTypes</code>的調用,其中546次是用于搜尋相适應的重載函數。
我所設計的算法,搜尋了全部168種可能性,但是根據我的分析,其最後隻産生了22次對<code>ConstraintSystem::matchTypes</code> 的調用。
去确定一個非公開的算法,需要進行很多次的猜測,是以知道某一種算法的的具體細節是一件非常困難的事兒。但是我想,我的算法在任意數量級的情況下,其表現優于或與現在已有的算法持平并不是一件不可能的事兒。
雖然我很想說:“我一個人就把所有工作做完了,看看這些代碼運作的多麼完美啊”,但是這也隻能是想想罷了。一個整個系統由成千上萬個邏輯和單元組成,并不能單獨抽出某一個節點來進行讨論。
你覺得 Swift 開發團隊是不是在嘗試把類型限制系統進行線性化處理呢?我對此持否定看法。
一點吐槽:
現在看來本文的前面兩章簡直就是在做無用功,我應該靜靜的将其删除。
我覺得我想法是正确的,類型限制系統應該進行大幅度改進,這樣我們次啊不會被上面所提到的問題所困擾。
友情提醒: 理論上将類型限制系統并不是整個語言最主要的一部分,是以如果其進行了改進,應該是在一個小版本疊代中進行釋出,而不是一個大版本更新。
在我使用 Swift 的經曆裡, <code>expression was too complex to be solved in reasonable time</code> 是一個經常出線的錯誤,而且我并不認為這是一個簡單的錯誤。如果你在單個例子中是用了大量的方法或者是數學操作的時候,你應該定期看看這篇文章。
Swift 裡所采用的時間複雜度為指數的算法也可能導緻編譯時間較長的問題。 盡管我沒有确切的統計整個編譯裡的時間配置設定,但是不出意外的話,系統應該将大部分時間放在了類型限制器的相關計算上。
這個問題可以再我們編寫的代碼的時候予以避免,但是講真,沒有必要這麼做。如果編譯器能采用我所提出的線性時間的算法的話,我敢肯定,這些問題都不在是問題。
在編譯器做出具體的改變之前,本文所提到的問題會一直困擾着我們,與編譯器的鬥争還要持續下去。
<b></b>
<b>原文釋出時間為:2016年08月02日</b>
<b>本文來自雲栖社群合作夥伴掘金,了解相關資訊可以關注掘金網站。</b>