iT邦幫忙

2025 iThome 鐵人賽

DAY 8
0

之前在 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 個指令可以分為四大類別,一一解釋如下。

  • log buffer

<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. 函數內部的區域變數在運行時的值。

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 語言開發時,這樣子做過:

  1. 對 pp 做定義跳轉,跳到 A 函式庫的源碼裡。
  2. 修改 pp ,不存檔直接對 buffer 求值。

如此, pp 的定義在直譯器裡就被修改了,已經可以輕易地來驗証假設了。

這邊要特別強調一點: 在 Neovim 裡,buffer 不等同於存在硬碟上的檔案。buffer 是你現在看到的檔案的狀態。

類似的技巧,在 Fennel 的情況需要微幅調整。

第一步的部分,Fennel 的跳轉定義通常無法處理函式庫只有 Lua 源碼而沒有 Fennel 源碼的狀況。這種時候,需要手動 grep 來找出正確的位置。

第二步的部分,找到的函數定義位置只有 Lua 檔時,可以修改 Lua 檔,並且用 Neovim 的 Ex 指令::lua [content] 來對當前修改的內容求值。

小結

本篇介紹了互動式開發使用 Conjure 的常用九個指令與兩個活用技巧。

有一句與奢侈品相關的諺語:

A luxury, once enjoyed, becomes a necessity.

互動式開發就是一種 Luxury ,它讓你開發軟體的靈活性抵達了另一個水準。你覺得這是必須品嗎?


工商時間

我的書《從試算表到資料平台》上架了,這是一本談論資料工程的書,歡迎各位讀者參考。


上一篇
Fennel 語言速成 -- nfnl 函式庫
下一篇
Lisp 深入淺出 -- S 表達式編輯
系列文
在 Neovim 中探索 Fennel 與函數式編程9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言