Elixir v1.20 發佈說明 / 有什麼新功能
Elixir v1.20 將該語言轉變為一種漸進式型別語言(gradually typed language)。主要的重點在於 Elixir 編譯器現在可以進行型別推斷,並檢查每個程式中的「已驗證的錯誤」(verified bugs)——即保證會在執行時失敗的型別違規——而不需要開發者為現有的程式碼添加任何型別註解。
集合論型別系統
Elixir 的新型別系統旨在具備健全性、漸進性且對開發者友善。它利用集合論型別(set-theoretic types),這意味著型別是由基本的集合運算組成:聯集、交集與否定。
dynamic() 型別與錯誤偵測
與許多漸進式型別系統中常見的、通常會停用型別檢查的 any() 型別不同,Elixir 的 dynamic() 型別會保留型別資訊。它具有兩個關鍵屬性:相容性與收窄(narrowing)。
- 相容性: 只有當提供的型別與接受的型別完全不相交時,才會回報型別違規。如果存在任何重疊(交集),則程式碼被視為相容。這可以防止在處理動態程式碼時,出現像靜態型別系統中常見的大量偽陽性(false positives)。
- 收窄: 型別系統可以在變數被使用時精煉型別。例如,如果一個變數被用作 map key 的存取 (
data.a),編譯器會將data的型別收窄為包含 key:a的 map。如果隨後的程式碼嘗試將該變數作為整數使用,編譯器會標記為違規,因為收窄後的型別(一個 map)與預期的型別(一個整數)是不相交的。
在 Guard 與 Clause 中的型別推斷
Elixir v1.20 在幾個核心語言建構式中實作了型別收窄與檢查:
Guards
編譯器現在可以從 guards 中推斷型別。例如,when is_list(x) 會推斷 x 為一個 list,而 when not is_map_key(x, :foo) 會推斷 x 是一个特別不包含 key :foo 的 map。它也會追蹤資料結構的大小;例如像 when tuple_size(x) < 3 這樣的 guard 會確保編譯器知道該 tuple 有至多兩個元素,從而防止無效的 elem/1 呼叫。
Case 語句與條件判斷
型別資訊會在不同的 clause 中進行精煉。在 case 語句中,如果第一個 clause 匹配 nil,編譯器就會知道在所有後續的 clause 中,該變數不再可能是 nil。這種收窄功能允許編譯器找出冗餘的 clause,並識別現有程式碼庫中的死碼(dead code)。
編譯效能與工具
Elixir v1.20 提升了編譯速度,特別是針對多核心機器。根據合成基準測試,Elixir 的建置工具現在是 BEAM 語言中最快的。
此外,新增了一個編譯器選項 :module_ تعريف(註:原文為 :module_definition),允許開發者將模組定義設定為 :interpreted(透過在 mix.exs 中設定 elixirc_options: [module_definition: :interpreted])。這可以在不影響寫入磁碟的最終 .beam 檔案的狀況下,減少大型專案的編譯時間。
未來發展藍圖
雖然 v1.20 提供了無需註解的型別推斷,但 Elixir 團隊仍在研究與開發顯式型別簽名(explicit type signatures)。這些將僅在滿足以下標準後才會引入:
v1.20 中型別系統的效能已獲得驗證。
遞迴型別(recursive types)的高效實作。
參數化型別(parametric types)的高效實作。
高效實作將 map key-value pairs 作為可列舉對象(enumerable)進行遍歷。
社群觀點
社群對此版本的發佈有褒貶不一,但總體而言是正向的。一些開發者讚賞這次更新提供的「免費」錯誤偵測功能,一位使用者說:
"It's very nice updating Elixir, having no breaking changes across my many projects and it then the compiler just finds bugs for free."
相反地,一些批評者認為,將型別引入一個最初並非型別語言的語言中,本質上不如 Gleam 或 Rust 等語言有效。其他人則質疑這種方法與 Dialyzer 的 "success typing" 如何比較,以及漸進式型別系統是否會影響程式的漸近效能(asymptotic performance)。