Lisp入门(好文)

来源:互联网 发布:中国的动漫公司知乎 编辑:程序博客网 时间:2024/05/16 11:15
Lisp 入门POSTED ON FEB 25, 2017心血来潮, 想学下 Lisp 或者 Haskell, 正好发现了Build Your Own Lisp 这本书, 所以先从 Lisp 入手.基本概念表达式要用括号 () 括起来, 括号中第一项内容为操作符, 后面的是它的操作域(操作数). 这种便是方法称为前缀表示法, 也叫波兰表示法.(+ 1 2)(sqrt 16)数学上, 函数用 f(x) 表示, 在 lisp 中, 也要使用前缀表示法表示:(f x) ;f(x)(g x y) ;g(x, y)(h (g x)) ;h(g(x))(< 3 4) ;比较大小, 3 < 4(and T T) ;true and true(or NIL T) ;false or true(or (< 3 4) (> 3 4)) ;3 < 4 or 3 > 4表Lisp 即所谓的 List procesor, 表处理语言. 上文中出现的括号, 都是表. 原子是不包含空格的字符, 不如上面的 +, sqrt, 操作数等等, 表里面 不见得都是原子元素, 表是可以嵌套的, 比如上面最后一个 or 操作, 就属于嵌套表.表的大小没有限制, 最简单的 () 是一个空表, 空表是 Lisp 里面既是表又 是原子的东西. 当原子讲的时候, 表示逻辑假(NIL). 上文中, 我们还遇到了 T, 这个表示逻辑真.程序和数据编程就是写一些程序, 处理数据. 在 Lisp 中, 程序和数据都用表来表示. 但是表示程序的表, 第一项一定是一个操作符, 否则就会报错:(+ 1 2) ;正常的 lisp 程序(1 2 3) ;这个第一项不是操作符, 会报错表示数据的表现不需要以操作符开头, 但是, 我们需要告诉 lisp 解释器, 不要试图计算这些数据表项的值, 这里用到了 quote 操作符.(quote (1 2 3))'(1 2 3)上面两种写法是等价的, 下面写起来更简单, 键盘会坏的慢一些.Lisp 里面不区分大小写, 如果我们在 REPL 里面输入 '(Hello World), 我们会得到 (HELLO WORLD) 作为输出.基本操作CAR 取表的第一项CAR 操作符用来提取表的第一项.(car '(1 2 3)) ;输出1(car '((1 2) 3)) ;输出(1 2)CDR 删除表的第一项CDR 用于删除表的第一项.(cdr '(1 2 3)) ;输出(2 3)(cdr '((1 2) 3)) ;输出(3)CXR 组合操作所谓 CXR 组合操作, 就是 CAR 和 CDR 的结合使用, 灵活使用, 可以取表的第二项, 第三项…(car (cdr '(1 2 3))) ;输出2(cadr '(1 2 3)) ; 同上, 输出2(cdr (car '((1 2) 3))) ;输出(2)(cdar '((1 2) 3)) ; 同上, 输出(2)类似的, 还可以组合出来CDAAR, CADDR … 一系列的操作出来. 作为初学者, 这么多括号我已经晕菜了, 难怪会有彩虹括号这种东西出现…CONS上面的操作是用来拆分一个表的, 现在试着用 cons 来合并表.(cons (1) 2) ;输出(1 . 2)(cons 1 (2)) ;输出(1 2)我们說括号扩起来的语句叫做表, 这种执行运算的也叫做 S-表达式, 表表示的实际上是一个二叉树, 在 S-表达式 里面, 这种二叉树记为 (Left . Right) 如果左支是一个表, 那么, 就会记做 ((Left) . Right), 如果右支是一个表, 那么记做 (Left . (Right)), 这种右支还可以把点号省略记做(Left Right)上面的 CDR 操作会删掉表的第一项, 实际上它做的工作是取出表除了第一项之外后面 的项, 按照上面的解释, 这个操作取的实际上是这个二叉树的右子树.APPEND 合并两个表cons 是在左右子树上进行的合并操作, append 将会在右子树上添加新的节点. 比如(append '(1 2) '(3 4)) ;输出(1 2 3 4) <=> (1 . (2 3 4))(cons '(1 2) '(3 4)) ;输出((1 2) 3 4) <=> ((1 2) . (3 4))LIST 函数list 函数会将所有传递给它的参数都放到一个表里面, 然后返回它.(list '(1) 2 3 '(4 5)) ;输出 ((1) 2 3 (4 5))再看原子原子可以是任何数, 分数, 小数, 自然数, 负数等等. 原子可以是一个字母排列, 当然其中 可以夹杂数字和符号. 空表就是原子NIL.判断一个元素或者一个字符是不是原子, 可以使用 atom 运算符.(atom 'a) ;输出 T(atom '(a b)) 输出: NIL变量赋值可以使用 setq 运算符. setq 运算符的作用就是将紧接着的变量赋值, 这个赋值语句的返回值是这个变量被赋予的值.(setq a 5)(+ a 7) ;输出 12(cons a '(1)) ;输出 (5 1)断言函数断言函数包括前面遇到的 atom 函数, 以及这里将要介绍的 null 和 equal 函数.null 函数判断一个表达式是否为 NIL, 是返回 T, 否则返回 NIL.equql 函数判断两个值是否完全相等.(null ()) ;输出T(null 'a) ;输出NIL(null (- 1 1)) ;输出NIL(equal 'a 'b) ;输出NIL(equal 'a 'a) ;输出T(equal (+ 1 2) 3) ;输出 T定义自己的函数defun 操作符用来定义自己的函数, 其形式为:(defun function_mame (arg1 arg2 ...) (function_body))函数的返回值是 function_body 运算完成之后的那个值. 比如:(defun addition (x y) (+ x y))(addition 1 2)在 REPL 输入以上代码, 将会得到加法运算结果: 3下面介绍两个系统自带函数: first, last.first 就是我们前面预见的 car 函数. cdr 对应的叫做 rest, 而这里的 last 取的是右子树(或者左子树, 如果没有右子树的话)最后一个节点.接下来我们定义一个函数, 取列表的首尾两个元素.(defun ends (l) (cons (first l) (last l)))条件操作符cond 表示条件操作符.cond 操作符有些复杂, 它的形式为(cond 分支列表1 分支列表2 分支列表3 ... 分支列表N)而其中分支列表的构成为 (条件p 值e)cond 操作符将对每一个”条件p”求值, 如果为NIL. 就接着求下一个, 如果为真, 就返回相应的”值e”, 如果没有一个真值, cond 操作符返回 nil. cond 操作 符的参数可以不止两个(cond (nil 1) (nil 2) (t 3)) ;输出3(cond (a 1) (nil 2) (t 3)) ;输出1利用条件操作符实现的最大值求值(defun max_number (a b) (cond ((> a b) a) (1 b)))上面这段代码也可以写成下面这样, 下面这段使用了 if 函数.(defun max_number (a b) (if (> a b) a b))递归函数比如要求等差数列 a(n) = a(n-1) + 2 的第 N 项. 那么, 递归函数可以定义为:(defun desq (n) (if (= n 0) 0 (+ (desq (- n 1)) 2)))递归函数这里 lisp 提供了一个 trace 函数来帮助查看函数调用栈.lisp> (trace desq)lisp> (desq 4)# 我们得到了一下输出1. Trace: (DESQ '4)2. Trace: (DESQ '3)3. Trace: (DESQ '2)4. Trace: (DESQ '1)5. Trace: (DESQ '0)5. Trace: DESQ ==> 04. Trace: DESQ ==> 23. Trace: DESQ ==> 42. Trace: DESQ ==> 61. Trace: DESQ ==> 88lisp 里面的7大公理(七个操作符)Lisp有7个基本操作符(实际上或许可以再精简). 这7个基本操作符就像几何中的 公理一样, 任何其他函数都可以由这七大公理定义. 也就是说, 7个基本操作符包 含了 Lisp 的所有语义.这7个基本操作符是:QuoteAtomEqCarCdrConsCond