Janet 程式語言:適合腳本與嵌入的現代 Lisp

Janet 是一種現代 Lisp 方言,優先考慮簡潔性、可嵌入性和易於分發。它被設計為一種具有第一類函式、詞法區塊作用域的命令式語言,且僅由八個主要指令組成的小型核心:dodefvarsetifwhilebreakfn

原生分發與二進位編譯

Janet 允許開發者將程式編譯為靜態連結 Janet 執行階段的原生可執行檔。此過程是透過將 Janet 程式碼編譯為位元組碼,將該位元組碼寫入一個初始化 Janet 執行階段的 .c 檔案,然後使用系統 C 編譯器編譯該 C 檔案來實現的。

此分發模式的主要優點包括:

  • 零依賴二進位檔:程式可以共享,無需最終使用者安裝 Janet 執行階段或專案依賴。
  • 小佔用空間:簡單的 "hello world" 二進位檔(Janet 1.27.0 在 aarch64 macOS 上)重量不到一兆字節(約 784K),已包含垃圾收集器和位元組碼編譯器。
  • 執行時評估:由於位元組碼編譯器已包含在二進位檔中,程式可以在執行時評估 Janet 程式碼。

進階文字解析與 PEGs

Janet 使用 Parsing Expression Grammars (PEGs) 取代傳統的正規表示式。PEGs 是結構化、可組合、第一類的解析器,相較於 regex 提供多項優勢:

  • 可預測性:它們更具可預測性且較少依賴行,允許無縫的多行文字解析。
  • 多功能性:PEGs 能解析非正規語言,例如 HTML 和 JSON,以及包含任意位元組零的二進位檔案格式。

雖然有些開發者主張 PEG 替代方案不具交換性,這可能會使除錯變得複雜,但它們仍是處理複雜文字時相較於標準正規表示式的強大替代方案。

嵌入與腳本介面

Janet 被設計為一個小型 C 函式庫,使其成為在較大應用程式中嵌入腳本介面的 Lua 可行替代方案。將 Janet 執行階段連結到 C 專案中,使開發者能夠使用一般 C 函式操作 Janet 值。

除了傳統軟體嵌入之外,Janet 也可以嵌入網站中,以建立具有可程式化 DSL 的靜態站點。此外,該語言內建了網路功能,這是其他微型解釋器常缺少的特性。

metaprogramming 與編譯時執行

Janet 透過宏提供強大的 metaprogramming 功能,並採用獨特的編譯時狀態保存方法。

Janet 宏允許開發者透過操作抽象語法樹(AST)來編寫產生其他程式碼的程式碼。為了保持參照透明性,Janet 允許在宏內取消引用 literal 函式。

編譯時至執行時值傳遞

Janet 最顯著的特色之一是能夠在編譯期間將程式的狀態序列化到磁碟。當程式被編譯時,Janet 會執行所有頂層指令並取得狀態的完整快照。此快照保存:

  • 共享參考:可變值在快照恢復後仍保持可變。
  • 生成器:生成器會記住其精確的指令狀態。
  • 閉包:閉包會在編譯時/執行時邊界上被保存。

這使得高效能最佳化成為可能,例如預先計算複雜資料(例如 reticulating splines)或透過在編譯時讀取檔案來嵌入資產。

語言設計與語法

Janet 離開 Lisp 傳統,以提升現代開發者的舒適度:

  • 命名慣例:它取代了傳統 Lisp 術語;例如,CARfirstPROGNdoLAMBDAfn,而 SETQset(儘管有些來源指出 def 用於不可變的綁定)。
  • 集合類型:該語言同時提供可變與不可變的集合。不可變集合使用值語義,而可變集合(前綴為 @)使用參考語義。
  • 語法糖:Janet 包含匿名函式的速記法(|(+ 1 $))、用於參數展開的 "splats"(;args),以及使用反引號來避免逃脫序列的靈活字面量字串。

社群見解與權衡

開發者間的討論凸顯了使用 Janet 時的一些實際考量:

  • 效能:使用者報告啟動時間非常快,有一位使用者透過 hyperfine 指出 1.4ms 的啟動時間,使其成為系統腳本相較於 dash (1ms) 的具競爭力替代方案。
  • Shell 整合sh 第三方函式庫提供了一個用於管道與重新導向的 DSL(例如 ($ find . -name *.janet | say)),一些使用者聲稱這使 Janet 成為 Bash、Python 或 Awk 在較長系統腳本上的可行替代方案。
  • 批評:有些使用者指出缺少套件管理版本控制以及進階函式庫的稀缺(例如複雜的 HTTP 路由)。其他使用者則表達了對靜態類型的偏好,或對 Lisp 方言普遍使用括號的厭惡。

Sources