跳至內容
GitHub 儲存庫 論壇 RSS 新聞訂閱

另一種語言

Ary Borenzweig

Crystal 具有全域類型推斷。除了少數需要的情況(泛型類型參數),您可以使用程式碼,而無需類型註解。

這是一把雙面刃。

一方面,不必明確指定類型真的很好。這使得原型設計非常快速,類似於動態語言。您可以快速草擬一個想法並進行演變,而無需不斷重新輸入類型。在重構和重新組織程式碼時,這也很有幫助,因為摩擦非常小。例如,當您從一段程式碼中提取一個方法時,您只需指定參數的名稱,編譯器將負責找出參數的類型和傳回類型。或者,您可以立即開始使用實例變數,只需為其指定一些值,而無需先宣告它及其可能包含的類型。

但是,這種方法也有一些缺點。讓我們分析一下它們。

程式碼變得難以理解和追蹤

有些人認為,沒有類型註解的程式碼會變得難以追蹤。讓我們看一個例子。

def sum(values)
  count = 0
  values.each do |value|
    count += value
  end
  count
end

values 的類型是什麼?如果不知道它操作的類型,人們如何理解這段程式碼?

我們相信,當您學習如何編寫程式時,您一定曾接觸過虛擬碼。它看起來類似於您在現實生活中找到的程式碼,只是它被簡化了:您在那裡很少看到類型註解。類型在給定的上下文中是顯而易見的。如果您新增類型註解和其他資訊,將會更難以理解程式碼的意圖。聽起來很熟悉嗎?

我們認為,Ruby 程式碼非常接近虛擬碼。在上面的程式碼中,沒有類型註解。演算法很清楚:迭代 values 中的每個項目,並將它們新增到 count 變數中。就是這樣。values 的類型是什麼?其實並不重要。我們關心的只是它是否可以迭代(使用 each),並且可以進行加總。此外,名稱 sumvaluescount 有助於理解方法的意圖和變數的可能類型。

將此與某些其他必須新增一些類型的語言進行比較

interface Iterable<T>
  def each(&block : T ->)
end

interface Addable<T>
  def +(other : T)
end

def sum(values : Iterable<T>) where T : Addable<T>
  count = 0
  values.each do |value|
    count += value
  end
  count
end

在這裡,我們告訴編譯器,有一個類型 Iterable,它有一個 each 方法,該方法產生泛型類型 T 的元素。然後我們還告訴編譯器,有一個類型 Addable<T>,它有一個方法 +,該方法使用其相同類型的值進行操作。最後,我們定義 sum 方法以對屬於 Iterable<T> 類型的值進行操作,其中每個 T 實作 Addable<T>

對我們來說,最後這段程式碼離我們腦海中的虛擬碼更遠。此外,還有更多程式碼需要閱讀和理解。

對此的一個可能的反駁是,現在更容易瀏覽程式碼。我想知道 each 是如何實作的,或者 Addable 是關於什麼的,而且我知道在哪裡可以找到它。如果沒有類型註解,我們就無法使用動態語言做到這一點。

但是…等等!Crystal 不是動態語言。當它完成編譯時,它會將類型指派給使用的每個變數和方法。從這些資訊中,我們可以重新建立程式碼並使其可瀏覽。事實上,如果您使用 crystal browser file.cr 編譯程式碼,Crystal 有一個(非常實驗性的)工具可以做到這一點。這將開啟一個網頁瀏覽器,您可以在其中查看變數的類型並瀏覽方法。因此,我們認為這在這裡不是一個有效的理由。

最後一點也表示 Crystal 知道類別實例變數的類型。在 Ruby 中,您可能會看到一個帶有 @foo 實例變數的類別,並想知道它的類型是什麼。別擔心,執行 crystal hierarchy file.cr,您就會知道確切的類型。如果您在規格檔案中執行它,這會非常有用,因為這些檔案顯示了類別的使用方式。將來,將有可能以 RDoc 之類的文件格式檢視此資訊。

無法進行增量編譯

因為沒有類型註解,所以編譯器需要每次從頭開始找出所有內容的類型。沒有辦法將模組編譯為物件檔或其他格式,然後重複使用該資訊,因為一開始沒有任何(類型)資訊。

幸運的是,Crystal 的編譯器速度非常快:編譯整個編譯器需要 5 到 10 秒的時間(其中只有 2.3 秒花在類型推斷階段)。對於較大的程式,時間將會變長,因此我們必須找到解決此問題的方法。

另一個問題是,很難做到 REPL。如果程式碼始終具有類型註解,我們就可以產生機器碼,而不必擔心變數的類型可能會發生變化,甚至是類型的實例變數被建立或更改。

很多時候,我們都想放棄。「如果我們要求程式設計師在實例變數和方法中新增類型註解,那麼增量編譯和 REPL 可能會成為現實」。 「至少語法會很令人愉悅」。幸運的是,每當我們其中一個人這樣說時,另一個人就會以一個大大的「不」回答。

這個「不」有一個非常強大的理由。如果我們朝那個方向改變語言,我們最終會得到另一種語言。Crystal 是一種您可以省略類型註解的語言(請注意:如果您真的想新增類型註解,則可以新增)。但是,如果您被迫新增它們,那麼它將不再是 Crystal。它可能會非常類似於現有的程式語言之一。為什麼我們要發明另一種與現有語言(或可能正在開發的語言)相似的語言呢?這有什麼好處?沒有。這將是浪費時間,重複的努力。

沒錯,如果我們不放棄,我們將面臨更艱鉅的挑戰。增量編譯真的不可能嗎?我們不能考慮類似的技術嗎?這種語言中是否可以以某種方式實現 REPL?出現了非常有趣的問題。出現了具有挑戰性的問題。快樂的時光來臨。

我們相信,可以存在一種編譯器不需要類型註解,但類型仍然存在的語言。我們想要一個聰明的編譯器。我們不想簡化語言來使我們編譯器撰寫者的工作更容易。我們想讓程式設計師的工作更輕鬆。而且更有趣 :-)