Elisp 入门1 - 打印

Elisp 入门2 - 语言概览

皮贝贝 posted @ 2011年1月01日 00:46 in emacs with tags Emacs elisp , 3354 阅读

Table of Contents

1 Emacs 工作流程

     Emacs 的工作实质是不停的阅读,求值,打印:

阅读 -—> 求值 —> 打印 —> 阅读 —> 求值 —> 打印 ……

     比如你在一个表达式的后面按下 C-x C-e 的时候, Emacs 会先读取光标前面的一个 S-表达式, 然后对其求值。如

(+ 12 13)

     在上面右括号的后面按下 C-x C-e, Emacs 读取光标前面的一个 S-表达式,是表 (+ 12 13), 然后求值得到 25, 打印在 message 窗口中。13后面按下 C-x C-e 时, Emacs 读取到前面一个S-表达式是符号 13, 由于对原子求值是其本身,所以打印出 13. Emacs 随时都可以将你缓冲区中的一段文字数据,作为一段程序来运行。

2 S-表达式

    我们看到,elisp 作为一种表处理语言,主要的数据结构是S-表达式。S-表达式可以是原子,也可以是表。

  • 原子: 最小单位,不可再拆分的类型。如基本的数值型, 字符串型, 符号,vector等等。
  • 表: 一个表可以递归的定义为括号内零个或n个元素的序列(每个元素是一个原子或一个表)(以空格分隔各元素): (元素1 元素2 … 元素n)

    对于表 '(I am "a pig") 来说,有三个原子组成

I, am, "a pig"

    而对于表 '(I am (a pig)), 有两个原子 I, am 和一个表 (a pig) 组成。

    通常,我们写表前面加一个单引号 "'", 这是函数 quote 的简写形式。函数返回后面的 S-表达式, 可以理解为对 S-表达式 不求值而直接返回打印它本身。

 

(+ 12 13)
 => 25
'(+ 12 13)
 => (+ 12 13)

 

    一个空表既是一个原子,也是一个表:

 

(atom '()) ;; 原子的判断谓词
 => t
(listp '()) ;; 表的判断谓词
 => t

 

2.1 求值

    对S表达式求值,对原子来说,返回原子的值。如

 

(setq a 13) ;; 符号 a 的值是整形 12
 
a
 => 13

 

    对表来说,求值比较有意思了,会将表中的第一项作为一个函数,后面的参数作为此函数的参数,求值就是执行这个函数。空表返回 nil.

 (+ 12 13)

    搜索到第一项是 + , 会在内存中找到这函数,传递2个参数12,13,然后这个函数求值为25,返回打印25。如果找不到函数符号,会提示出错 void-function.

3 nil 和 t

    Elisp 中并不存在布尔类型,但是可以判断一个对象是不是真值和假值,就是和对象 t, nil 做比较。编程中,有一个规律,判断一个值的真假,和假值做比较,因为和真值做比较会出现意外的结果。和 nil 做比较的函数是 null, 可以 C-h f 查看文档说明。

    nil 和 t 是 Elisp 中最重要的两个对象。 这里的真假指的是对象的真假,而非对象的值的真假。0 是真值, 因为 0 是以整形对象存储。""也是真,以字符串对象存储。相反,一个空的对象,nil 和 空表就是假值。记住在 elisp 中,除了 nil 和 '() ,其余都是真值。

 

(null "") ;; 空字符串是真
 => nil
(null 0) ;; 0 是真
 => nil
(if 0 (message "True") (message "False"))
 => "True" 
(/= 0 3)
 => t
(null '()) ;; 空表
 => t
(null '(a b)) 
 => nil

 

    做判断是和假值做比较,那么非假值就可以返回计算结果了:

 

(and nil 12)
 => nil
(and 0 12)
 => 12
(or nil (message "如果是假值那么打印我吧")) ;; 这里的 or 就有 if 的味道了
 => "如果是假值那么打印我吧" 

 

3.1 谓词

    Elisp 中的谓词指的是那些返回的结果是 t 或 nil 的那些函数。 前面的 null, atom, listp 就是谓词。用以判断是否符合某些条件。

 

(atom '()) ;; 是否是原子
(listp '()) ;; 是否是表
(null 12) ;; 是否是假值
(floatp 12) ;; 是否是浮点型数值
(integerp 12);;是否是整形数值

 

    大多数谓词通常以 p 做后缀.

4 变量

    lisp 中符号是指有名字的对象,在内部是由一个表来维护着名字和对象之间的联系。我们可以通过名字来获得对象的值。而且,对一个符号来说,我们可以更改其指向的对象,轻快的如同将一件物体的标签接下来贴到另一个物体上,如此简单! 这个就是变量。

    我们设置一个变量:

 

(set 'foo 12)

 

    就是我们现在内部表中寻找 foo 这个名字,如果找到将其关联的对象指针指向整形12;如果没找到,先创建一个 foo 符号,其对象指针指向 12. 这个操作太普遍了,所以set 有一个简单的变体形式:

 

(setq foo 12) ;; 不用在写单引号了
(setq foo 12
        go "12") ;; setq 还可以给几个变量赋值

 

5 控制结构

 

5.1 顺序 progn

(progn BODY…)

    progn 按顺序执行其参数语句,返回的是最后一个参数的执行结果

 

(progn (setq a 12) (message "sdf") (message "last"))
 => "last" 

 

5.2 循环 while

(while TEST BODY…)

    如果 TEST 为真,执行 BODY…。 如此重复,直到 TEST 为假,返回 nil。

 

(setq a 12)
 => 12
;; 循环移植到 a 不大于0 
(while (> a 0)
        (setq a (- a 1)))
 => nil
a
 => 0

 

5.3 判断 if

(if COND THEN ELSE…)

    如果 COND 为真执行 THEN, 返回 THEN 的计算结果。 如果 COND 为假, 执行 ELSE… ,并返回 ELSE… 最末一个表达式的结果。 THEN 有且只有一个表达式, ELSE… 为0个或一个表达式。如果 COND 为假,且没有 ELSE… 表达式,返回 nil.

 

(if t (message "真"))
 => "真" 
(if nil (message "真"))
 => nil
(if (< 12 0) (message "12>0") (message "12<0"))
 =>"12<0" 
(defun g-max (a b)
        "求最大值" 
        (if (> a b) a b))
(g-max 12 13)
 => 13

 

5.4 分支 cond

(cond CLAUSES…)

    会尝试每个条件,直到一个匹配。每个 CLAUSES 是形如 (CONDITION BODY…) 的表,BODY… 为0个或多个表达式。如果找到一个 CONDITION 为真的,会计算 BODY… 并返回最末一个结果。反之返回 nil.

 

(defun age-interval (age)
        "返回你的年龄区间" 
        (cond 
        ((< age 10) "童年")
        ((< age 20) "少年")
        ((< age 35) "青年")
        ((< age 45) "中年")
        ((< age 60) "中老年")
        (t "老年")))
(age-interval 23)
 => "青年" 

 

6 let 函数

(let VARLIST BODY…)

    如 :

 

(setq  a 12)
 => 12
(let ((a 15) (b 12))
        (message "a=%d, b=%d" a b))
 => "a=15, b=12" 
a
 => 12

 

    可以看到参数列表中的参数绑定不会影响外面的变量,这就是局部变量。

7 函数

    函数在 elisp 中也是一个普通对象,我们可以绑定到一个符号上去。而关联的符号名就是这个函数名。

 

(type-of 'null)
 => symbol
(functionp 'null)
 => t

 

    一个函数的定义包括名字,参数列表,函数文档,交互选项,函数体。函数用 defun 定义:

 

(defun FUNCTION-NAME (ARGUMENTS...)
  "OPTIONAL-DOCUMENTATION..."
  (interactive ARGUMENT-PASSING-INFO)     ; optional
  BODY...)
  • 函数文档是函数的功能描述,可选。我们在 C-h f 查询函数的时候查询到的就是这个函数的文档描述。
  • 交互选项,可选。我们在 Emacs 中使用 M-x 执行的函数,都是交互的。

    我们定义一个简单的加法函数, C 里是这样的:

 

int add (int a, int b)
{
        return a + b;
}

 

   我们用 elisp 是这个样子:

 

(defun add (a b)
       "函数 add 用来计算参数的和" 
       (+ a b))

    在末尾按下 C-x C-e, 然后我们的函数就算安装完成了。C-h f add 我们就看到我们的函数文档了。执行一个函数:

 

(add 12 13)
 => 25

    函数作为符号,设置用 fset:

 

(fset 'abc 'add)
(abc 12 13)
 => 25

8 实例

 

8.1 判断闰年

    闰年的定义如下:

  • 对非世纪年份(不能被100整除),能被4整除的为闰年
  • 对世纪年份(能被100整除的),能被400整除的为闰年

 

(defun leap-year-p  (year)
        "判断年份是否为闰年" 
        (if (= 0 (mod year 100)) ;; 是否是世纪年份
            (= 0 (mod year 400)) ;; 世纪年份被 400 整除
                (= 0 (mod year 4)))) ;; 非世纪年份被4整除

    同 0 比较如此常见,有了另一个方便的谓词: zerop, 一个 zerop的版本:

 

(defun leap-year-p  (year)
        "判断年份是否为闰年" 
        (if (zerop (mod year 100)) ;; 是否是世纪年份
            (zerop (mod year 400)) ;; 世纪年份被 400 整除
                (zerop (mod year 4)))) ;; 非世纪年份被4整除
 
(leap-year-p 1900)
 => nil
(leap-year-p 1904)
 => t

8.2 斐波那契数列

    斐波那契数列的递归定义如下:

  • F0 = 0
  • F1 = 1
  • Fn = Fn-1 + Fn-2

    求数列的第n项程序:

 

(defun fib-nth (n)
        "求斐波那契数列的第n项值" 
        (cond 
           ((= n 0) 0)
           ((= n 1) 1)
           (t (+ (fib-nth (- n 1)) (fib-nth (- n 2))))))
 
(fib-nth 10)
 => 55

 


 


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter