Robert Chassell:an Introduction To Programming In Emacs Lisp@2009 (文档版本 3.10)
提供資料是一本針對非程式設計師的 Emacs Lisp 程式設計入門書。其核心論點在於逐步引導讀者掌握 Emacs Lisp 的基本概念與實作技巧,最終使其能夠理解 Emacs 的運作原理、撰寫自訂功能、並利用 Emacs 本身作為學習工具來深入探索程式設計。本書透過介紹 Emacs Lisp 的基礎結構、核心功能、以及與 Emacs 環境互動的方式,旨在賦予讀者自行擴充 Emacs 功能的能力,並培養閱讀及理解現有 Emacs Lisp 原始碼的技能。以下是從書中提取並詳盡解釋的主要論點:
一、 Lisp 語言的基礎結構與評估機制
本書的核心論點之一是強調 Lisp 語言的獨特結構——S-表達式(S-expressions),它是程式碼和資料的統一表示方式。一個 S-表達式可以是原子(atom),如數字(例如 37)、符號(例如 +, foo, forward-line)或字串(例如 "這是字串");也可以是列表(list),由括號 () 包圍並由空白分隔的 S-表達式組成(例如 (加 2 2))。即使是包含其他列表的列表,也遵循相同的基本結構,例如 '(這個 列表 有 (另一個 列表))'。
Lisp 程式的執行過程就是對 S-表達式進行「評估」(evaluation)。評估規則如下:
1. 被引用的 S-表達式(例如 ' (+ 2 2)):單引號 ' 是 quote 函數的縮寫,它告訴 Lisp 解譯器不要評估後面的表達式,直接返回該 S-表達式本身。這是表示資料列表的常用方式。
2. 未被引用的列表(例如 (+ 2 2)):Lisp 解譯器會將列表的第一個元素視為一個函數(function)或特殊形式(special form)的名稱。它會查找該名稱對應的定義(一組電腦指令),並將列表其餘元素的評估結果作為引數傳遞給該函數或特殊形式,然後執行其定義中的指令。例如,(+ 2 2) 會呼叫 + 函數,並將 2 和 2 的評估結果(即數字 2 本身)作為引數,最終返回 4。
3. 未被引用的原子:
* 數字或字串:評估結果是其本身。
* 符號:Lisp 解譯器會嘗試查找該符號所綁定的變數值,並返回該值。如果符號沒有綁定值(即「變數為空 void-variable」),則會發出錯誤。
這種簡單而一致的結構,讓 Lisp 程式碼本身看起來就像是資料結構,這是 Lisp 強大且獨特之處,也使得 Lisp 程式可以很容易地操作或生成其他 Lisp 程式。書中特別介紹了 Lisp 的幾個基礎操作列表的函數:car(取得列表的第一個元素)、cdr(取得列表除第一個元素外的其餘部分)、以及 cons(將一個元素加到列表的開頭,建構新的列表)。這些函數雖然名稱來源古老,但它們定義了列表處理的基本邏輯,是理解 Emacs Lisp 中列表如何表示和操作的關鍵。
二、 Emacs Lisp 的核心功能與程式建構
本書深入講解如何在 Emacs Lisp 中定義和使用函數,這是撰寫 Emacs 自訂功能的基礎。
1. 函數定義(defun):使用 defun 這個特殊形式來定義函數。一個函數定義包含:
* 函數名稱(一個符號)。
* 引數列表(一個包含符號的列表,用於接收傳入的引數值)。
* 說明文件字串(documentation string),解釋函數的功能,方便使用者(和程式設計師)理解。這是 Emacs 的重要特色,可透過 C-h f(describe-function)查看。
* 可選的 interactive 表達式:這是一個特殊形式,用於聲明函數可以被使用者以互動方式呼叫(例如透過 M-x 後輸入函數名稱,或透過綁定的按鍵)。interactive 可以接收引數,指示 Emacs 如何從使用者那裡獲取引數值(例如從前綴引數 C-u 獲取數字,或提示使用者輸入字串)。
* 函數的主體(body):一個或多個 S-表達式,它們是函數實際執行的指令序列。
-
變數(Variables)與作用域(Scope):符號不僅可以綁定函數定義,也可以綁定變數值。使用
setq(set的變體,會自動引用第一個引數符號)來為符號設定值,例如(setq count 0)。書中強調了符號的函數定義和變數值是獨立的,一個符號可以同時擁有函數定義和變數值。為了管理變數的作用域,let特殊形式被引入,用於創建僅在特定程式區塊內有效的區域變數(local variables)。let綁定的變數會遮蔽外部同名的變數,其值在let表達式結束時失效。這避免了不同函數或程式碼塊之間的變數名稱衝突。let*是let的變體,允許在綁定變數時,後面的變數可以使用前面剛綁定的變數的值。 -
基本控制流程:
-
條件判斷(
if):if特殊形式用於實現 if-then-else 邏輯。它評估第一個引數(測試條件),如果結果為真(非nil),則評估第二個引數(then-part);否則,評估第三個引數(else-part,如果存在)。Emacs Lisp 中,除了nil以外的所有值都被視為「真」。 -
重複執行(
while,dolist,dotimes):while特殊形式根據條件重複執行其主體,直到條件評估為nil。dolist和dotimes是常用的巨集(macro),提供了更方便的列表遍歷和固定次數循環機制,它們是while的簡化寫法。
-
條件判斷(
三、 與 Emacs 環境的深度互動
Emacs Lisp 的一個關鍵應用是與 Emacs 編輯器環境緊密整合,這是其作為可擴充編輯器的核心能力來源。本書詳細介紹了緩衝區(buffer)、游標位置(point)、標記(mark)和區域(region)等 Emacs 的核心概念,以及 Emacs Lisp 程式如何操作它們。
1. 緩衝區操作:緩衝區是 Emacs 中用於儲存文本的物件,與硬碟上的檔案(file)不同,緩衝區中的修改需要保存才會反映到檔案。buffer-name 函數返回當前緩衝區的名稱,buffer-file-name 返回緩衝區訪問的檔案名稱。current-buffer 返回當前活動的緩衝區物件本身。set-buffer 用於改變 Emacs 程式操作的緩衝區焦點,而 switch-to-buffer 除了改變焦點,還會更新視窗顯示,通常用於使用者互動。
2. 游標與標記:point 函數返回游標的當前位置,以從緩衝區開頭算起的字元數表示(從 1 開始)。point-min 和 point-max 返回當前緩衝區(或受限區域)的最小和最大位置。mark 是另一個在緩衝區中標記的位置,C-SPC(set-mark-command)設定標記。游標和標記之間的文本稱為區域(region)。goto-char 函數用於移動游標到指定的位置。push-mark 在當前游標位置設定標記,並將舊標記壓入標記環(mark ring)。
3. 狀態保存與恢復:在 Emacs Lisp 程式中,函數可能會移動游標或切換緩衝區,這可能會讓使用者感到困惑。save-excursion 特殊形式用於保存游標和標記的當前位置以及當前緩衝區,在其主體執行完畢後自動恢復這些狀態。這對於編寫不會意外改變使用者介面的函數至關重要。save-restriction 特殊形式類似,用於保存和恢復緩衝區的受限(narrowing)狀態。
4. 文本操作(剪下、複製、貼上):Emacs 的文本操作函數(如 kill-region, copy-region-as-kill, yank)是 Emacs Lisp 如何操作緩衝區文本的典型範例。被「殺掉」(kill)的文本會被儲存在殺戮環(kill ring)這個列表結構中,以便後續可以透過 yank(貼上)命令取回。本書透過分析這些函數的原始碼,揭示了如何使用 car, cdr, cons 等列表操作函數以及 setcar, setcdr 等修改列表結構的函數來管理殺戮環這個列表。
四、 進階程式設計技巧與 Emacs Lisp 生態系
本書涵蓋了 Emacs Lisp 程式設計的一些進階技巧,並介紹了與 Emacs 環境更深層互動的方式。
1. 遞歸(Recursion):遞歸是一種程式設計技巧,函數在其定義中呼叫自身。遞歸函數通常包含一個基本情況(base case)來終止遞歸,以及一個遞歸步驟(recursive step)來將問題分解為更小的子問題並呼叫自身。本書透過遍歷列表和數值計算的例子來解釋遞歸的原理和實作方式。
2. 正規表達式搜尋(Regular Expression Search):正規表達式是定義文本模式的強大工具。Emacs Lisp 提供了 re-search-forward 和 re-search-backward 等函數,用於在緩衝區中根據正規表達式進行搜尋。這些函數通常與條件判斷或循環結合使用,用於定位特定文本或執行模式匹配操作(例如尋找句子或段落的結尾),這在文本處理函數中非常常見。
3. 除錯(Debugging):本書介紹了 Emacs Lisp 的除錯工具,包括內建的 debug 除錯器和來源碼層級的 edebug 除錯器。這些工具允許程式設計師逐步執行程式碼、檢查變數值、設定中斷點,以找出程式中的錯誤(bugs)。掌握除錯技巧對於開發和維護 Emacs Lisp 程式至關重要。
4. .emacs 初始化檔案與客製化:本書的核心應用之一是指導讀者如何利用 ~/.emacs(或 ~/.emacs.el)這個個人初始化檔案來客製化 Emacs 環境。~/.emacs 檔案包含 Emacs Lisp 程式碼,會在 Emacs 啟動時被評估執行。透過在這個檔案中撰寫 Lisp 表達式,使用者可以設定變數(例如改變填充欄 fill-column 的值)、綁定或重新綁定按鍵(global-set-key, define-key)、載入外部 Emacs Lisp 函式庫(load),甚至定義自己的函數。defvar 和 defcustom 是用於定義變數的巨集,特別是 defcustom 支援 Emacs 的客製化(Customize)介面,讓使用者可以透過選單或表單來修改變數值,而無需直接編輯 Lisp 程式碼。自動載入(autoloading)是一種優化啟動速度的機制,它允許函數在第一次被呼叫時才載入其定義所在的檔案。
透過學習這些核心概念和技巧,讀者能夠從 Emacs 的使用者轉變為 Emacs 的擴充者,理解現有功能的實現方式,並有能力根據自己的需求修改或創建新的 Emacs 功能,真正體現 Emacs 作為「可擴充環境」的精髓。書中穿插的程式碼範例和對現有 Emacs Lisp 原始碼的導覽,是培養這些能力的關鍵。
comments
comments for this post are closed