Janet 程式語言:適合腳本與嵌入的現代 Lisp
Janet 是一種現代 Lisp 方言,優先考慮簡潔性、可嵌入性和易於分發。它被設計為一種具有第一類函式、詞法區塊作用域的命令式語言,且僅由八個主要指令組成的小型核心:do、def、var、set、if、while、break 和 fn。
原生分發與二進位編譯
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 術語;例如,
CAR是first,PROGN是do,LAMBDA是fn,而SETQ是set(儘管有些來源指出def用於不可變的綁定)。 - 集合類型:該語言同時提供可變與不可變的集合。不可變集合使用值語義,而可變集合(前綴為
@)使用參考語義。 - 語法糖:Janet 包含匿名函式的速記法(
|(+ 1 $))、用於參數展開的 "splats"(;args),以及使用反引號來避免逃脫序列的靈活字面量字串。
社群見解與權衡
開發者間的討論凸顯了使用 Janet 時的一些實際考量:
- 效能:使用者報告啟動時間非常快,有一位使用者透過 hyperfine 指出 1.4ms 的啟動時間,使其成為系統腳本相較於
dash(1ms) 的具競爭力替代方案。 - Shell 整合:
sh第三方函式庫提供了一個用於管道與重新導向的 DSL(例如($ find . -name *.janet | say)),一些使用者聲稱這使 Janet 成為 Bash、Python 或 Awk 在較長系統腳本上的可行替代方案。 - 批評:有些使用者指出缺少套件管理版本控制以及進階函式庫的稀缺(例如複雜的 HTTP 路由)。其他使用者則表達了對靜態類型的偏好,或對 Lisp 方言普遍使用括號的厭惡。