閱讀340 返回首頁    go 阿裏雲 go 技術社區[雲棲]


我也說說Emacs吧(6) - Lisp速成

前麵我們學習了基本操作,也走馬觀花地看了不少emacs lisp的代碼。這一章我們做一個lisp的速成講座。

Lisp的含義是表處理語言。它的代碼組成結構都是用括號組成的表來表示的。Lisp中的功能,要麼是以函數形式求值,要麼本身就是一些特殊表。
比如在Lisp語言中,判斷分支的if不是語句,也不是函數,而是一種特殊的表。定義函數的方式,也是用一種叫做defun的特殊表。

Lisp基本函數速成

首先我們搭建一下環境,隨便建一個.el為擴展名的文件。然後,我們寫一個helloworld的代碼吧:

(message (concat "Hello" "," "World" "!"))

我們把光標停留在這行上,然後執行C-x C-e,或者執行命令eval-last-sexp,結果就會在下邊的狀態欄上打印Hello,World.

下麵函數的實驗,我們就都采用這個方式來進行。

查找函數的幫助

將光標放到要查詢的函數上,比如在message中,運行C-h f,就可以查詢message函數的幫助文檔。

算術函數

加法 +

例:

(message "%d" (+ 1 1))

減法 -

(message "%d" (- 1 1))

乘法 *

例:

(message "%d" (* 123456789 987654321))

注意,在emacs lisp中,數字是會溢出的。
例錯誤:

(message "%d" (* 123456789 3145926535897932384626433832795928841971))

會報下麵的錯:

  debug(error (overflow-error "3145926535897932384626433832795928841971"))

除法 /

需要注意的是,在emacs lisp中,除法結果是整數:

(message "%d" (/ 128.1 3.0))

結果是42

求餘 %

需要注意的是,如果不是整數求餘的話,會報錯的。

例:

(message "%d" (% 128.1 3.0))

會報下麵的錯:

Debugger entered--Lisp error: (wrong-type-argument integer-or-marker-p 128.1)
  %(128.1 3.0)
  (message "%d" (% 128.1 3.0))
  eval((message "%d" (% 128.1 3.0)))

如果不知道integer-or-marker-p函數的含義,還是老辦法,光標移到過去,然後運行C-h f(describe function)去查詢它的文檔。

加1 1+

例:

(message "%d" (1+ 1))

等價於

(message "%d" (+ 1 1))

減1 1-

與1+完全類似。
例:

(message "%d" (1- 1))

數學函數

求e的階乘,exp函數

(message "%d" (exp 10))

結果是22026,請注意結果是整數。

求階乘函數 expt

(message "%d" (expt 2 8))

結果是256,2的8次方。

求對數函數 log

如果給兩個參數,那麼就log a b,是求以b為底的a的對數。如果省略b,則默認值為e.

(message "%d" (log 256 2))
(message "%d" (log 100))

輸出為8和4.

平方根函數 sqrt

(message "The square root of 128 is:%d" (sqrt 128))

輸出為:

"The square root of 128 is:11"

求絕對值函數

(message "The absolute vaule of -1 is:%d" (abs -1))

輸出為:

The absolute vaule of -1 is:1

三角函數

sin, cos, tan都是例行公事的函數,反函數是asin, acos, atan,

(message "%d" (sin 0))
(message "%d" (cos 0))
(message "%d" (tan 1))
(message "%d" (asin 1))
(message "%d" (acos 1))

邏輯運算

  • 兩數邏輯與 logand
  • 兩數邏輯或 logior
  • 兩數邏輯異或 logxor
  • 兩數邏輯非 lognot

例:

(message "%d" (logand 1 0))
(message "%d" (logior 1 0))
(message "%d" (logxor 1 0))
(message "%d" (lognot 0))

第一個1與0為0. 第二個1或0為1. 第三個1與0異或為1. 第四個0取非是-1.

邏輯運算特殊表

在實際編程中,用於與或非邏輯運算的是幾個特殊表:

  • 與 and: 對每個參數進行求值,直到遇上一個nil
  • 或 or: 對每個參數進行求值,直到遇上一個非nil
  • 非 not:not是nil別名。其實取反就是判斷一個邏輯值是否為nil.

比較函數

  • =: 等於
  • >: 大於
  • <: 小於
  • >=: 大於或等於
  • <=: 小於或等於
  • /=: 不等於
  • eq: 等於

例:

(/= (logand 1 0) (lognot 1))

判斷函數

  • atom: 判斷是不是一個原子
  • listp: 判斷是不是一個列表
  • null: 判斷是不是空
  • stringp: 判斷是不是一個字符串
  • characterp: 判斷是不是一個字符
  • symbolp: 判斷是不是一個符號
  • zerop: 判斷是不是0.
  • intergerp: 判斷是不是整數
(atom ())
(listp ())
(null ())
(stringp "Hello")
(characterp "h")
(symbolp nil)
(numberp 1)
(zerop 0)
(integerp 1)

if特殊表

學習了若幹判斷函數,我們當然需要一個控製結構來使用它,這個控製結構就是if特殊表。if是個特殊表,而不是函數,當然對於我們初學使用來說,這個區別並不重要。
if特殊表的結構是(if 條件判斷 THEN表 ELSE表 ... )
如果判斷為非nil,則執行THEN表,否則執行ELSE表。
我們看個例子:

(if (atom ()) (message "() is an atom") (message "() is not an atom"))

輸出當然是:() is an atom

cond特殊表

當條件特別多時,if特殊表嵌套可能會導致控製結構比較亂。這時我們可以使用cond特殊表來解決,cond特殊表的結構為:(cond 表1 表2 ...)
cond特殊表的會一直執行後麵的表,直至遇到任何一個表的值為非nil為止。
每個cond中的表都是由兩部分組成:判斷條件和其他值。cond特殊表執行時,會首先檢查判斷條件是否nil,如果為nil則繼續執行,否則就返回非nil表後麵的值。
我們還是通過一個例子來學習:

 (cond ((= x y) "x and y are the same")
    ((> x y) "x is greater than y")
    ((< x y) "x is less than y"))

定義函數 - defun特殊表

學習了這麼多預定義的函數,我們是不是也躍躍欲試,打算寫幾個自己定義的函數了呢?
Emacs Lisp的函數定義要比Common Lisp多幾樣東西。因為我們前麵講了,emacs lisp的函數就是我們正常調用的命令,所以它要定義交互方式,要定義函數文檔。不過好在這兩部分都是可選的,完全不知道的情況下,仍然能寫出可以正確執行的函數來。

emacs lisp的defun特殊表的格式如下:
(defun 函數名 (形參列表) "可選的函數文檔" (interactive;可選的交再繼續方式)
函數體)

我們先寫個簡版的,實現判斷一個數是不是偶數的函數:

(defun evenp (x)
  (if (= 0 (% x 2)) t
    nil))
(evenp 2)

我們再將其加上描述文檔,同時加上一個是不是整數的判斷:

(defun evenp (x)
  "Check if x is an even number or not"
  (if (and (integerp x) (= 0 (% x 2))) t
    nil))

到目前為止,雖然看起來用括號不太習慣,但是我們已經學會用defun特殊表定義函數,用if和cond特殊表來實現判斷。好像也沒什麼複雜的嘛?

學習了之後再回頭看我們之前貼過的代碼,是不是一下子就好懂了很多呢?
比如這個set-mark-command,不就是幾個cond特殊表的組合麼:

(defun set-mark-command (arg)
...
  (cond ((eq transient-mark-mode 'lambda)
     (kill-local-variable 'transient-mark-mode))
    ((eq (car-safe transient-mark-mode) 'only)
     (deactivate-mark)))
  (cond
   ((and (consp arg) (> (prefix-numeric-value arg) 4))
    (push-mark-command nil))
   ((not (eq this-command 'set-mark-command))
    (if arg
    (pop-to-mark-command)
      (push-mark-command t)))
   ((and set-mark-command-repeat-pop
     (eq last-command 'pop-global-mark)
     (not arg))
    (setq this-command 'pop-global-mark)
    (pop-global-mark))
   ((or (and set-mark-command-repeat-pop
             (eq last-command 'pop-to-mark-command))
        arg)
    (setq this-command 'pop-to-mark-command)
    (pop-to-mark-command))
   ((eq last-command 'set-mark-command)
    (if (region-active-p)
        (progn
          (deactivate-mark)
          (message "Mark deactivated"))
      (activate-mark)
      (message "Mark activated")))
   (t
    (push-mark-command nil))))

遇到具體的函數,我們都可以通過describe-function函數去查看它的文檔和代碼。

我們先隻求會用,有個感性的認識,之後基礎回頭再補。

小結

幾個特殊表:

  • if特殊表:用來進行判斷
  • cond特殊表:多分支判斷
  • and特殊表:求值直到遇見nil為止
  • or特殊表:求值直到遇見非nil為止
  • defun特殊表:用來定義函數

有了上麵的知識,分支、子程序結構都有了,我們可以像寫命令式語言一樣寫代碼了。

最後更新:2017-06-13 02:31:50

  上一篇:go  Maven項目構建工具使用
  下一篇:go  MaxCompute(原ODPS)開發入門指南——數據開發工具篇