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

Ruby 開發者使用 Crystal 的五大理由

Mark Siemers

這是一篇由 Mark Siemers 撰寫的客座文章,他自願提供一系列 Crystal 部落格文章的建議。請期待更多內容,或者 - 更好的是 - 聯絡我們 撰寫您自己的文章。

1. 極低的學習曲線

想想過去 5-10 年來流行的程式語言。你會想到什麼?Elixir、Go、Rust 嗎?它們在效能上都比 Ruby 有優勢,但學習和掌握起來更困難。

如果能夠以更簡單的學習曲線獲得效能提升呢?

有多簡單?我們來看看一些程式碼。

問: 以下哪個模組是用 Ruby 寫的?哪個是用 Crystal 寫的?

module Year
  def self.leap?(year)
    year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)
  end
end
module Hamming
  def self.distance(a,b)
    a.chars.zip(b.chars).count{|first, second| first != second }
  end
end

答: 這是個陷阱題 - 兩者都是。上面的模組可以在 RubyCrystal 中執行。是不是很酷?

更多程式碼相似的範例請見這裡

這並不表示所有 Ruby 程式碼都能在 Crystal 中執行(反之亦然),但您可以在 Crystal 中立即完成許多工作,並且在第一天,甚至第一分鐘就具有生產力。

Crystal(一種強型別和編譯語言)如何表現得像 Ruby(一種動態和鴨子型別語言)?Crystal 的編譯器結合了兩種強大的技術:型別推斷聯合型別。這讓編譯器能夠讀取您的類 Ruby 程式碼並找出(推斷)要使用的正確型別。

除了相似之處,Crystal 還提供了一些比 Ruby 更核心的優勢。例如...

2. 編譯時檢查和方法多載

當您在 Ruby 中寫 is_a?respond_to? 來確保程式碼不會中斷時,是否覺得很 hacky?您是否曾經擔心所有您沒有放入這些檢查的地方?這些都是等待發生的錯誤嗎?

Crystal 是一種編譯語言,會在編譯時檢查您所有的方法輸入和輸出。如果任何型別不一致,它們會在執行時之前被捕獲。

讓我們重新看看上面的 Year::leap? 範例。在 Ruby 中,當輸入不是整數時會發生什麼事?

Year.leap?("2016") #=> false
Year.leap?(Date.new(2016, 1, 1)) #=> undefined method `%' for #<Date: 2016-01-01 ... >

對於 String 我們會得到錯誤的答案,對於 Date 我們會得到執行時例外。在 Ruby 中修復這些問題至少需要一個 is_a? 陳述式

module Year
  def self.leap?(input)
    if input.is_a? Integer
      input % 400 == 0 || (input % 100 != 0 && input % 4 == 0)
    elsif input.is_a? Date
      input.leap?
    else
      raise ArgumentError.new("must pass an Integer or Date.")
    end
  end
end

您覺得這個方法怎麼樣?我們仍然有可能發生執行時例外,只是錯誤訊息更有幫助。

在 Crystal 中,我們可以選擇明確地輸入我們的輸入(和輸出)。我們可以將方法簽章變更為 self.leap?(year : Int),並保證輸入的是整數。

我們會在編譯時而不是執行時收到有用的訊息

Year.leap?("2016")
Error in line 10: no overload matches 'Year.leap?' with type String
Overloads are:
 - Year.leap?(year : Int)

如果我們想在我們的模組中新增對 Time 的支援(想想 Ruby 中的 DateTime),我們可以多載 Year::leap?

module Year
  def self.leap?(year : Int)
    year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)
  end

  def self.leap?(time : Time)
    self.leap?(time.year)
  end
end

與 Ruby 一樣,方法多載允許輸入的靈活性,但沒有鴨子型別的猜測。編譯時檢查可以防止型別不符錯誤進入生產環境。

說到生產環境,那麼...

3. 極快的效能

編譯的另一個優點是速度和最佳化。在比較 Ruby 和 Crystal 的效能時,通常可以用數量級而不是百分比來表示。

在一個範例中,在 Crystal 中加總隨機數字的速度可能比 Ruby 快 10 個數量級(快約 3,700 萬 %)。這是由於編譯器最佳化和在 Crystal 中使用基本資料型別的能力。這確實存在大數整數溢出的風險(請參閱 Ary 的解釋)。

Crystal 的內建 HTTP 伺服器在基準測試中能夠處理超過每秒 200 萬個請求。許多 Web 框架也持續為 Web 應用程式提供毫秒級以下的響應時間。

這引導我們到下一個重點...

4. 您想要的 Web 框架已經在這裡

喜歡 Rails(甚至 Elixir 的 Phoenix)的完整性嗎?您會在 Amber 框架中感到賓至如歸。

Sinatra 的簡潔性和易於自訂是否更符合您的風格?您會發現 Kemal 的簡潔性。

您想要一個利用編譯時檢查來進行強型別參數、HTTP 動詞和資料庫查詢的全端框架嗎?今天是您幸運的一天。

在 1 月期間,這些 Web 框架中的每一個都將在自己的專屬文章中重點介紹。請回來查看 Crystal 部落格以瞭解更多資訊。

5. Crystal 是用 Crystal 寫的!它很容易理解,並且可以為該語言做出貢獻

您讀過 Ruby 的原始碼嗎?嘗試找出一些 Enumerable 方法是如何運作的嗎?

Ruby 的 Enumerable#all? 實作

static VALUE
enum_all(int argc, VALUE *argv, VALUE obj)
{
    struct MEMO *memo = MEMO_ENUM_NEW(Qtrue);
    rb_block_call(obj, id_each, 0, 0, ENUMFUNC(all), (VALUE)memo);
    return memo->v1;
}

要弄清楚該程式碼在做什麼需要多長時間?如果您從未接觸過 C 程式碼,可能需要很長的時間。

將其與 Crystal 的 Enumerable#all? 實作進行比較

def all?
  each { |e| return false unless yield e }
  true
end

弄清楚這一點需要多長時間?如果您了解 Ruby 或 Crystal,可能只需要幾秒鐘。

考慮到這一點,請思考 98.4% 的 Crystal 是用 Crystal 寫的,只有 0.3% 是用 C++ 寫的。

Ruby 有 30.6% 是用 C 寫的,64.8% 是用 Ruby 寫的。

事實上,Crystal 中只有一個檔案是用 C++ 寫的,所以只要您沒有變更 LLVM 擴充功能,您在 Crystal 語言中尋找的任何東西幾乎都可以保證是用 Crystal 寫的。

讀取、理解和貢獻 Crystal 比您能找到的任何語言都容易。

從哪裡開始?

如果您想試試看 Crystal 程式語言,以下是一些入門的好資源