美文网首页
0基础——lisp学习笔记(三)

0基础——lisp学习笔记(三)

作者: ayusong870 | 来源:发表于2020-06-12 08:38 被阅读0次

    目录:

    1. Hello,world
    2. A Simple Database
    3. 语法和语义(待补充)
    4. 函数(Functions)
    5. 变量
    6. 序列变量的基本操作
    7. 标准宏
    8. 自定义宏(Macors)
    9. 数字、字符和字符串
      参考文献

    7. 标准宏

    7.1. WHEN和UNLESS(IF)

    在介绍WHEN和UNLESS之前,首先详细介绍下已经用过很多次的IF宏。IF宏的语法为:
    (if condition then-form [else-form])
    else-form为选择书写部分,如果condition为NIL而没有写else-form的话将直接返回NIL。另外当then-form段不止一句代码时要用PROGN将多段代码括上,例如:

    (if (spam-p current-message)
        (progn
          (file-in-spam-folder current-message)
          (update-spam-database current-message)))
    

    这样扩展写起来很不舒服,那么就用到WHEN宏了:

    (when (condition)
      (form1....)
      (form2....))
    

    实际上WHEN宏就是用IF构建的宏:

    (defmacro when (condition &rest body)
      `(if ,condition (progn ,@body)))
    

    对应WHEN宏的是UNLESS,当条件不满足时执行语句,UNLESS的IF实现如下:

    (defmacro unless (condition &rest body)
      `(if (not ,condition) (progn ,@body)))
    

    UNLESS语法

    (when (condition)
      (form1....)
      (form2....))
    

    7.2. COND

    在其他语言中通常有IF——ELSE IF——ELSE IF——ELSE这样的结构,在lisp中也可以这样写:

    (if a
        (do-x)
        (if b
           (do-y)
           (do-z)))
    

    如果需要多条语句,那么又要写很多的PROG,因此LISP推出了COND宏。

    (cond
      (test-1 form*)
          ...
      (test-N form*))
    

    7.3. AND、OR、NOT

    与或非三种逻辑结构在判断语句中最为常用,下面是一些样例:

    (not nil)             ==> T
    (not (= 1 1))         ==> NIL
    (and (= 1 2) (= 3 3)) ==> NIL
    (or (= 1 2) (= 3 3))  ==> T
    

    7.4. 循环(DOLIST和DOTIMES)

    Dolist宏可以遍历list中的元素,基本格式如下:

    (dolist (var list-form)
      body-form*)
    

    当循环开始lisp遍历list-form中的每一个元素,然后执行body-form*中的代码,下面是样例:

    CL-USER> (dolist (x '(1 2 3)) (print x))
    1
    2
    3
    NIL
    

    当满足某些条件不再需要循环时,可以使用RETURN跳出。

    CL-USER> (dolist (x '(1 2 3)) (print x) (if (evenp x) (return)))
    1
    2
    NIL
    

    DOTIMES宏有点类似于C语言中的for语句,格式和样例如下:

    (dotimes (var count-form)
      body-form*)
    CL-USER> (dotimes (i 4) (print i))
    0
    1
    2
    3
    NIL
    

    DOTIMES也可以使用RETURN来跳出循环,下面的样例求解1到20的乘法表:

    (dotimes (x 20)
      (dotimes (y 20)
        (format t "~3d " (* (1+ x) (1+ y))))
      (format t "~%"))
    

    7.5. 循环DO

    DOLIST和DOTIMES是针对一些简单的循环应用,写代码比较方便,如果复杂的循环,还是要使用DO宏。

    (do (variable-definition*)
        (end-test-form result-form*)
      statement*)
    

    Variable-definition段用来定义变量,end-test-form用来判定结束条件,result-form表示结束时执行的内容,statement为每一层循环的代码。
    每一条variable-definition
    格式如下:
    (var init-form step-form),var为变量名,init-form为初始值,step-form为下一循环的值。
    当end-test-form为T时,执行result-form,最后一条执行语句得到的值作为DO宏的返回值。
    下面的语句计算了Fibonacci数字:

    ? (do ((n 0 (+ 1 n))
    (cur 0 next)
    (next 1 (+ cur next)))
    ((= 10 n) cur))
    55
    

    但是要技术,虽然利用step-form的方法做循环主体计算也是可以的但是,最好遵循DO的标准模式,在statement*中执行主体计算。

    7.6. 非常强大的LOOP宏

    最简单的LOOP宏,没有任何变量定义仅有循环体:

    (loop
      body-form*)
    

    样例:

    (loop
      (when (> (get-universal-time) *some-future-date*)
        (return))
      (format t "Waiting~%")
      (sleep 60))
    

    下面思考一个例子,如果要收集1到10用do如何实现:

    (do ((nums nil) (i 1 (1+ i)))
        ((> i 10) (nreverse nums))
      (push i nums)) ==> (1 2 3 4 5 6 7 8 9 10)
    

    用loop for就简单很多了,这是loop的扩展应用:

    (loop for i from 1 to 10 collecting i) ==> (1 2 3 4 5 6 7 8 9 10)
    

    如果要计算1到10的平方和:

    (loop for x from 1 to 10 summing (expt x 2)) ==> 385
    

    统计元音(aeiou)在字符串中出现的次数:

    (loop for x across "the quick brown fox jumps over the lazy dog"
          counting (find x "aeiou")) ==> 11
    

    计算先前写过的Fibonacci数字:

    (loop for i below 10
          and a = 0 then b
          and b = 1 then (+ b a)
          finally (return  a)) 
    

    符号across, and, below, collecting, counting, finally, for, from, summing, then, and to都是loop的关键字用来扩展LOOP功能,将在后面的LOOP章节详细介绍。

    8. 自定义宏(Macors)

    宏是一段会在编译时生成的代码,例如下面的代码,在函数中用到WHEN宏:

    (defun foo (x)
      (when (> x 10) (print 'big)))
    

    这段代码的目的是为了更好地给编程者阅读或书写,而实际上when宏的内容会在编译时将when替换掉,when宏的定义是这样的:

    (defmacro when (condition &rest body)
      `(if ,condition (progn ,@body)))
    

    那么替换后foo代码实际上是这样的:

    (defun foo (x)
        (if (> x 10) (progn (print 'big)))
    

    8.1. 定义宏DEFMACRO

    通过DEFMACRO来定义新的宏(DEFMACRO全称Definition for Mac, Read Only。Mac是实现宏功能的人的名字)。

    (defmacro name (parameter*)
      "Optional documentation string."
      body-form*)
    

    定义宏的格式基本与函数相同,总的来说编写宏的过程如下:
    1、向宏和它应该扩展的代码写入一个示例调用,反之亦然。
    2、在示例代码中将生成的扩展代码写出来。
    3、确保宏抽象不缺失。

    8.2. 样例: do-primes

    do-primes函数用来输出从start到end中的所有素数。那么可以这样写:

    (defun primep (number)
      (when (> number 1)
        (loop for fac from 2 to (isqrt number) never (zerop (mod number fac)))))
    (defun do-primes (start end)
      (loop for p from start to end do
        (when (primep p) (format t "~d " p))))
     (do-primes 0 20)
    2 3 5 7 11 13 17 19
    

    但是这样写,我们能对素数做的就仅仅是输出了,而且输出的格式也改变不了,如果想对得到的素数做一些其他的事情呢?并且是想做什么就做什么呢?
    在简易数据库的联系中,我们了解了宏的功能是可以拼接代码,并且可以执行作为宏参数传递进去的代码。那么现在讲do-primes改成宏,为了实现可以对每一个素数操作,那么要将loop for p中的p通过参数来命名,(format t “~d ” p)这句功能代码也通过参数来传递。
    do-primes宏的参数就要有(var start end)以及&rest body组成,在循环中通过`符号来实现代码拼接:

     `(loop for ,var from ,start to ,end do
         (when (primep ,var) ,@body)))
    

    最终得到的宏函数为:

    (defmacro do-primes ((var start end) &rest body)
      `(loop for ,var from ,start to ,end do
         (when (primep ,var) ,@body)))
    

    现在实现将0到100的素数相加,并在每找到一个素数时输出素数以及当前的和,很神奇吧:

    ? (setf sum-primes 0)
    ? (do-primes (p 0 100) (setf sum-primes (+ sum-primes p)) (format t " add primes ~d sum is ~d ~%" p sum-primes))
     add primes 2 sum is 2
     add primes 3 sum is 5
     add primes 5 sum is 10
     add primes 7 sum is 17
     add primes 11 sum is 28
     add primes 13 sum is 41
     add primes 17 sum is 58
     add primes 19 sum is 77
     add primes 23 sum is 100
     add primes 29 sum is 129
     add primes 31 sum is 160
     add primes 37 sum is 197
     add primes 41 sum is 238
     add primes 43 sum is 281
     add primes 47 sum is 328
     add primes 53 sum is 381
     add primes 59 sum is 440
     add primes 61 sum is 501
     add primes 67 sum is 568
     add primes 71 sum is 639
     add primes 73 sum is 712
     add primes 79 sum is 791
     add primes 83 sum is 874
     add primes 89 sum is 963
     add primes 97 sum is 1060
    

    9. 数字、字符和字符串

    9.1 TABLE 1

    CL-USER> 10
    10
    CL-USER> 20/2
    10
    CL-USER> #xa
    10
    

    9.2 TABLE 2

    123                            ==> 123
    +123                           ==> 123
    -123                           ==> -123
    123.                           ==> 123
    2/3                            ==> 2/3
    -2/3                           ==> -2/3
    4/6                            ==> 2/3
    6/3                            ==> 2
    #b10101                        ==> 21
    #b1010/1011                    ==> 10/11
    #o777                          ==> 511
    #xDADA                         ==> 56026
    #36rABCDEFGHIJKLMNOPQRSTUVWXYZ ==> 8337503854730415241050377135811259267835
    

    9.3 TABLE 3

    1.0      ==> 1.0
    1e0      ==> 1.0
    1d0      ==> 1.0d0
    123.0    ==> 123.0
    123e0    ==> 123.0
    0.123    ==> 0.123
    .123     ==> 0.123
    123e-3   ==> 0.123
    123E-3   ==> 0.123
    0.123e20 ==> 1.23e+19
    123d23   ==> 1.23d+25
    

    9.4 TABLE 4

    #c(2      1)    ==> #c(2 1)
    #c(2/3  3/4)    ==> #c(2/3 3/4)
    #c(2    1.0)    ==> #c(2.0 1.0)
    #c(2.0  1.0d0)  ==> #c(2.0d0 1.0d0)
    #c(1/2  1.0)    ==> #c(0.5 1.0)
    #c(3      0)    ==> 3
    #c(3.0  0.0)    ==> #c(3.0 0.0)
    #c(1/2    0)    ==> 1/2
    #c(-6/3   0)    ==> -2
    

    9.5 TABLE 5

    (+ 1 2)              ==> 3
    (+ 1 2 3)            ==> 6
    (+ 10.0 3.0)         ==> 13.0
    (+ #c(1 2) #c(3 4))  ==> #c(4 6)
    (- 5 4)              ==> 1
    (- 2)                ==> -2
    (- 10 3 5)           ==> 2
    (* 2 3)              ==> 6
    (* 2 3 4)            ==> 24
    (/ 10 5)             ==> 2
    (/ 10 5 2)           ==> 1
    (/ 2 3)              ==> 2/3
    (/ 4)                ==> 1/4
    

    9.6 TABLE 6

    (+ 1 2.0)             ==> 3.0
    (/ 2 3.0)             ==> 0.6666667
    (+ #c(1 2) 3)         ==> #c(4 2)
    (+ #c(1 2) 3/2)       ==> #c(5/2 2)
    (+ #c(1 1) #c(2 -1))  ==> 3
    

    9.7 TABLE 7

    (+ (* (floor    (/ x y)) y) (mod x y)) === x
    (+ (* (truncate (/ x y)) y) (rem x y)) === x
    

    9.8 TABLE 8

    (incf x)    === (setf x (1+ x)) === (setf x (+ x 1))
    (decf x)    === (setf x (1- x)) === (setf x (- x 1))
    (incf x 10) === (setf x (+ x 10))
    (decf x 10) === (setf x (- x 10))
    

    9.9 TABLE 9

    (= 1 1)                        ==> T
    (= 10 20/2)                    ==> T
    (= 1 1.0 #c(1.0 0.0) #c(1 0))  ==> T
    

    9.10 TABLE 10

    (/= 1 1)        ==> NIL
    (/= 1 2)        ==> T
    (/= 1 2 3)      ==> T
    (/= 1 2 3 1)    ==> NIL
    (/= 1 2 3 1.0)  ==> NIL
    

    9.11 TABLE 11

    (< 2 3)       ==> T
    (> 2 3)       ==> NIL
    (> 3 2)       ==> T
    (< 2 3 4)     ==> T
    (< 2 3 3)     ==> NIL
    (<= 2 3 3)    ==> T
    (<= 2 3 3 4)  ==> T
    (<= 2 3 4 3)  ==> NIL
    

    9.12 TABLE 12

    (max 10 11)    ==> 11
    (min -12 -10)  ==> -12
    (max -1 2 -3)  ==> 2
    
    Numeric Analog Case-Sensitive Case-Insensitive
    = CHAR= CHAR-EQUAL
    /= CHAR/= CHAR-NOT-EQUAL
    < CHAR< CHAR-LESSP
    > CHAR> CHAR-GREATERP
    <= CHAR<= CHAR-NOT-GREATERP
    >= CHAR>= CHAR-NOT-LESSP
    Literal Contents Comment
    "foobar" foobar Plain string.
    "foo"bar" foo"bar The backslash escapes quote.
    "foo\bar" foo\bar The first backslash escapes second backslash.
    ""foobar"" "foobar" The backslashes escape quotes.
    "foo\bar" foobar The backslash "escapes" b
    CL-USER> "foo\"bar"
    "foo\"bar"
    CL-USER> (format t "foo\"bar")
    foo"bar
    NIL
    
    Numeric Analog Case-Sensitive Case-Insensitive
    = STRING= STRING-EQUAL
    /= STRING/= STRING-NOT-EQUAL
    < STRING< STRING-LESSP
    > STRING> STRING-GREATERP
    <= STRING<= STRING-NOT-GREATERP
    >= STRING>= STRING-NOT-LESSP
    (string= "foobarbaz" "quuxbarfoo" :start1 3 :end1 6 :start2 4 :end2 7)
    (string/= "lisp" "lissome") ==> 3
    (string< "lisp" "lisper") ==> 4
    (string< "foobar" "abaz" :start1 3 :start2 1) ==> 5   ; N.B. not 2
    

    参考文献

    1. 《Practical Common Lisp》,Peter Seibel http://www.gigamonkeys.com/book/

    相关文章

      网友评论

          本文标题:0基础——lisp学习笔记(三)

          本文链接:https://www.haomeiwen.com/subject/jhwlkhtx.html