之前在 day02 ,我們就先介紹過了 Lisp 、互動式開發還有 S 表達式編輯。接下來,我們要來更加深入探討 Lisp ,而本日的重點先放在互動式開發。
之前我們曾經介紹了 Conjure 的 Normal Mode 指令 ,ee
。這邊先做個釐清,嚴格來講,Conjure 所綁定的指令是:<localleader>ee
。
打開 ~/.config/nvim/init.vim
檔,可以看到一行,它將 <localleader>
定義成了 ,
。
let maplocalleader=","
這就是為什麼我們會用 ,ee
的原因。讀者可以根據自己的喜好,將<localleader>
設為其他按鍵。
除了先前提到的:
<localleader>ee
:對當前游標所在的表達式,求值。(evaluate expression)。:ConjureEval [code]
:對當前的 [code]
,求值。而常用的 Conjure 指令還有:
<localleader>lv
:開啟 log buffer 視窗。(log in vertical window)<localleader>lq
:關閉 log buffer 視窗。(log quit)<localleader>lr
:清除 log buffer 內的內容。(log reset)<localleader>eb
:對當前 buffer 的內容,求值。(evaluate buffer)<localleader>ew
:對當前游標所在的字串,求值。(evaluate word)<localleader>gd
:對當前游標所在的字串,跳轉定義。(go to definition)K
:對當前游標所在的字串,查詢文件。(doc)這些指令的記憶,其實都有英文的線索。比方說,<localleader>lv
開啟的視窗是直立式的,所以可以聯想到 log in vertical window 。比較要轉個彎聯想的,是文件的英文是 documentation ,而平常就講 doc 而已,所以對應到 K 。
Conjure 還有更多的指令嗎?有的,官方的文件 裡還有更多指令。還有需要研究更多嗎?這邊總共談了 9 個 Conjure 指令,應該夠一位專業的 Lisp programmer 使用五年了。
前述 9 個指令可以分為四大類別,一一解釋如下。
<localleader>lv
, <localleader>lq
, <localleader>lr
顯然是 log buffer 的控制。log buffer 是 Conjure 在眾多 Vim 的互動式開發插件裡突出的設計之一。log buffer 的一大優點是:即使你做了求值之後,返回的資料量大到不行,通常 Neovim 也不會卡住。資料就在 log buffer 裡。
<localleader>ee
, :ConjureEval [code]
, <localleader>eb
, <localleader>ew
這四個指令,它在 Conjure 的底層都是透過同樣的結構去跟直譯器做溝通。比方說,<localleader>ee
在 Conjure 裡的實作,就是先讀取一個表達式,再把其內容送去給直譯器;<localleader>eb
的話,則是先讀取整個 buffer 的內容,再其內容送去給直譯器。上述兩個指令的後半部都是相同的行為。
它會對直譯器送出查詢定義的特殊指令。特別注意一點,目前最新版的 Conjure 裡,Fennel 語言的跳轉功能尚未實作。另一方面,我們還是可以透過 LSP (language server protocol) 搭配安裝 fennel-ls 來得到跳轉定義的功能
它會對直譯器送出查詢文件的特殊指令。這邊補充說明一點,K
是 Neovim 的內建指令,用來查詢游標所在位置的關鍵字文件。而 Conjure 則巧妙地將這個指令整合,讓 K
能自動向 Lisp 直譯器發送查詢請求,以便我們查詢函式的說明文件(documentation)。
在 day03 ,我們有談到
由於採用互動式開發的關系,Lisp programmer 幾乎是隨時開著 Debugger
難道說互動式開發可以當 Debugger 使用嗎?幾乎可以。
Debugger 對除錯的輔助,通常就三大類:
1 和 2 顯然可以用互動式開發取代。3 的話,要繞個小彎。這邊要來介紹一個技巧:內聯檢查 (Inline Inspection)。
考慮以下的程式碼,當我們想要了解 z 這個區域變數的值的時候,似乎只有 print
一招。而 print
討厭的點在於,當印出來的東西太多時,我們還要透過肉眼去尋找印出的標的。
(fn wrong-add [x y]
(let [z (+ x 2)]
(+ z y)))
在 Fennel 中,我們可以利用 tset(table set)來「捕捉」運行時的區域變數值,並將其寫入一個全域的 Table,以便我們隨時檢查。這個技巧背後的核心思想是:透過修改一個全域可存取的狀態,來觀察函數內部的區域變數。
下方改寫的重點在於:加入一個 (local ddd {})
於最上方,並且用 (tset ddd :z z)
去補捉運行時的 z 值。
(local ddd {})
(fn wrong-add [x y]
(let [z (+ x 2)]
(tset ddd :z z)
(+ z y)))
(wrong-add 3 4)
當我們對整個檔案用 <localleader>eb
求值後,將游標移到 ddd 的上方,下指令 <localleader>eb
,就可以看到結果。
; eval (word): ddd
{:z 5}
這個技巧在不同 Lisp 方言中有不同的實現方式,例如在 Clojure 中,通常會使用 def 來達到類似效果。
想像一下,你開發軟體時,使用了一個函數 pp 引用自 A 函式庫。你強烈地懷疑 pp 有錯,但是,該怎麼測試這件事來驗証假設呢?修改函式庫嗎?會不會有點費工,尤其當 A 還是已經安裝好的 jar 檔時。
我曾經在 Clojure 語言開發時,這樣子做過:
如此, pp 的定義在直譯器裡就被修改了,已經可以輕易地來驗証假設了。
這邊要特別強調一點: 在 Neovim 裡,buffer 不等同於存在硬碟上的檔案。buffer 是你現在看到的檔案的狀態。
類似的技巧,在 Fennel 的情況需要微幅調整。
第一步的部分,Fennel 的跳轉定義通常無法處理函式庫只有 Lua 源碼而沒有 Fennel 源碼的狀況。這種時候,需要手動 grep 來找出正確的位置。
第二步的部分,找到的函數定義位置只有 Lua 檔時,可以修改 Lua 檔,並且用 Neovim 的 Ex 指令::lua [content]
來對當前修改的內容求值。
本篇介紹了互動式開發使用 Conjure 的常用九個指令與兩個活用技巧。
有一句與奢侈品相關的諺語:
A luxury, once enjoyed, becomes a necessity.
互動式開發就是一種 Luxury ,它讓你開發軟體的靈活性抵達了另一個水準。你覺得這是必須品嗎?
我的書《從試算表到資料平台》上架了,這是一本談論資料工程的書,歡迎各位讀者參考。