欢迎来到Lisp

0. Lisp系统的交互式前端(REPL)


刚接触Lisp,会发现每种Lisp的实现都会带有REPL(read - eval - print -loop)。
这里记录一下REPL相关的说明及问题。

  • 顶层(toplevel)
    在任何lisp系统中的交互式前端(repl),叫做顶层

  • 中断循环(break loop)
    如果输入了一些Lisp无法理解的东西,它会打印出一个错误信息,然后进入一个叫做
    中断循环的顶层。
    中断循环给予有经验的程序员一个找出错误原因的大集会,不过对于初学者可能只能
    跳出这个循环了。
    每个Lisp实现的REPL的中断循环跳出的方式不通,请根据提示使用。

1. 形式(Form)


Lisp中使用S表达式进行程序的书写。通过左括号( 表示表达式开始,右括号 )表示表达式结束。
正式因为使用了这种表达式,所以Lisp的语法十分简单,因为除了像1这种原子外,都是括号中的表达式。

2. 求值(Evaluation)


Lisp使用前缀表达式,类似(+ 1 2)这种形式。括号内的第一个元素是函数,后面的就是函数的参数。

Lisp中使用quote作为一种保护表达式不被求值的方式,quote也可以缩写为'

3. 数据(Data)


Lisp提供了在其他预言中找的到的及找不到的数据类型。如,整数(integer)、字符串(string)等其他语言中可以找到的数据类型。还有如,符号(symbol)与列表(lists)等,在其他语言中找不到的数据类型。

  • 符号(通常) 不对自己身求值,所以需要使用quote引用它。

  • 列表是由被括号包裹住的零个或多个元素来表示的。括号内的元素可以是任何类型。
    使用列表不许要引用(quote或者其缩写'),不然Lisp会以为是一个函数调用。

  • list函数可以用来创建列表。但不同的是,由于list是函数,所以它的实参自然会被求值。

4. 列表操作(List Operations)


  • cons函数用来构造列表

  • car函数获取列表的第一个元素

  • cdr函数获取列表除第一个元素外的所有元素

  • third函数可以获取列表的第三个函数,通过的还有first、second等等。

5. 真与假(Truth)


在Common Lisp中,t表示逻辑真nil表示逻辑假
将返回值逻辑真逻辑假的函数称为谓词(predicate)。所以谓词的名字通常以p结尾。

  • listp函数判断是否为列表。
  • null函数判断是否为空(假)。
  • not函数对参数进行逻辑非判断。
  • if条件,通常接受三个表达式,一个test表达式,一个then表达式和一个else表达式。若test为逻辑真则对then表达式求值,并返回表达式的值。
    否则,对else表达式求值,并返回表达式的值。
    if的else实参是选择性的,如果不写,默认是nil。
  • and宏对任意参数进行逻辑与判断。
  • or宏对任意实参进行逻辑或判断

6. 函数(Functions)


使用defun定义新函数,Lisp不对程序(program)过程(procedure)函数(function)做区别。

7. 递归(Recursion)


函数自己调用自己称为递归

  • eql函数(谓词)测试两个实参是否相等
  • member测试第一个实参(元素)是否为第二个实参(列表)中的成员。

8.阅读Lisp(Reading Lisp)


使用缩进来阅读及编写程序,不要只盯着括号看。任何好的编辑器都会又括号匹配的功能。

9. 输入输出(Input and Output)


  • format格式化输出
  • read标准输入函数

10. 变量(Variables)


  • let可以创建新的局部变量,在特定的上下文中使用。
    let表达式有两个部分
    第一部分为一组创建新变量的指令,形式为(variable expression)。
    第二部分为一个有表达式的函数体,在函数体内最后一个表达式的求值结果为let的返回值。

  • numberp(谓词)用来测试实参是否为一个数字

  • defparameter用来创建一个全局变量,全局变量的命名通常以开始,并以结束。

  • defconstant用来创建全局常量。不需要给全局常量一个独一无二的名字,因为如果相同名字存在,会产生重复定义的错误。

  • boundp函数用来检查实参是否为全局变量全局常量

11. 赋值(Assignment)


  • setf是赋值操作符,可以给全局或局部变量赋值。
    setf的第一个实参可以是表达式变量名
    如果setf的第一个实参是符号(symbol),并且不是某个局部变量的名字,则setf会把这个符号设为全局变量。也就是隐式的创建全局变量。
    但是还是推荐使用defparamter明确地创建全局变量。

12.函数式编程(Functional Programming)


函数式编程意味着撰写利用返回值工作的程序,而不是修改程序中的某些东西。换言之,只使用函数的返回值,而不使用函数的副作用。

函数式编程允许使用交互式测试(interactive testing)

13.迭代(Iteration)


当想要重复做一些事情的时候,迭代比递归来的更自然。

  • do宏是Common Lisp中最基本的迭代操作符。

    • 第一个实参是一组变量说明列表,(variable initial update)。
      其中variable是符号,initial与update是表达式。变量会被赋予initial表达式的值 作为初始值。每一次迭代,都会被赋予update表达式的值。
    • 第二个实参包含一个货多个表达式。其中,第一个表达式用来测试迭代是否结束。
      而之后的表达式则被依次求值,直到迭代结束。其中最后一个值会被当作do的返回值返回。
    • 其余的实参则组成了循环的函数体。每次迭代,都会被依序求值。
  • progn接受任意数量的表达式,依序求值,并返回最后一个表达式的值。

  • dolist接受该形式的实参(variable expression),并在后边跟着一个具有表达式的函数主体。函数主体会被求值,而变量相继与表达式所返回的列表元素绑定。

14. 函数作为对象(Functions as Objects)


Lisp中,函数与符号、字符串一样是第一类对象。

  • function特殊操作符返回接受实参关联的对象,缩写为#',称为升引号(sharp-quote)。
    升引号后接函数名便可以将函数当作实参传给函数,function +#'+

  • apply接受一个函数和一个实参列表,并返回把传入函数应用到实参列表的结果。
    apply接受任意数量的实参,最后一个是列表即可。

  • funcall与apply的效果相同,只是不需要把实参包装成列表。

  • lambda创建匿名函数使用 (funcall #'(lambda (x) (+ x 100)) 1)

15. 类型(Types)


在Common Lisp中,数值才是又类型的,而变量没有。
类型又fixnumintegerrationalreal、numberatomt
其中t是所有类型的基类(supertype)

  • typep函数接收一个对象和一个类型,然后判断对象是否为该类型

习题(Exercises)


  • (a) 14
  • (b) '(1 5)
  • (c)7
  • (d)'(nil 3)
  • (cons 'a '(b c))
  • (cons 'a (cons 'b '(c)))
  • (cons 'a (cons 'b (cons 'c ())))
(defun our-get-fourth (lst)
        (car (cdr (cdr (cdr lst)))))
(defun our-max (first-num second-num)
      (if (> first-num second-num)
        first-num second-num))
  • (a)遍历x,遍历完毕后,返回一个nil
  • (b)在y中查找x,如果找到,则返回元素在列表中的位置,如果没有找到则返回nil。
  • (a)x为car就可以得到相同的结果
  • (b)x为or可以得到相同的结果
  • (c)x为apply可以得到相同的结果
  (defun our-list-element (lst)
    (if (null (car lst))
      nil
      (if (listp (car lst))
        t
        (our-list-element (cdr lst)))))
  • (a)
    • 递归版本
      (defun print-point-rec (num)
        (if (zerop num)
          'done
          (progn
            (format t "~A" ".")
            (print-point-rec (- num 1)))))
      
    • 迭代版本
      (defun print-point-ite (num)
        (do ((i num (- i 1)))
          ((zerop i) 'done)
          (format t "~A" ".")))
      
      
  • (b)
    • 递归版本
      (defun count-list-rec (lst)
        (if (null lst)
           0
          (+ 1
             (count-list (cdr lst)))))
      
    • 迭代版本
      (defun count-list-ite (lst)
        (let ((len 0))
          (dolist (obj lst)
            (setf len (+ 1 len)))
            len))
      
  • (a)该函数接受一个数字列表是没问题,但是如果接受的是空列表或字符列表,则会报错。

  • (b)该函数没有结束条件,不论传入何种列表都会死循环。

  • 更改后为

    (defun summit-second (lst)
       (let ((x (car lst)))
         (if (null x)
         0
         (if (numberp x)
           (+ x (summit-second (cdr lst)))
           (summit-second (cdr lst))))))

你可能感兴趣的:(欢迎来到Lisp)