Lisp五法十诫

| 分类 lisp之基础语法  深入学习之设计模式  | 标签 lisp  scheme  racket  clojure  语法  五法  十诫  七宗罪  摩西  经验  孔子  孙子  设计模式  函数式  S-表达式  递归 

古时智者将自己对社会的观察、人生的经验归纳总结,或者形成一些名言警句、俗语谚语,或者输出一套方法论,以规范自己的行为、指导后人、警醒后世……

孔子的“己所不欲勿施于人”、“学而不思则罔,思而不学则殆”等

image

孙子在《孙子兵法》中对于军事领域战略战术的系统性总结

image

老外们也有自己的一套价值观和方法论!

《圣经》中记载上帝在西奈山的山顶将“十诫”亲自传达给摩西,是上帝对以色列人的告诫

image

13世纪,道明会神父圣多玛斯·阿奎纳列举出各种人类恶行的表现,总结出人性的“七宗罪”

image

在计算机领域也有一些经验性的总结!《设计模式》中的23种面向对象编程套路就是前人基于开发中常见的一些问题,以及针对这些问题的典型的编程模式进行总结的结果。在《The Little Schemer》一书中,作者也对Lisp编程的套路进行了一次总结!

先学会一些基础的套路,然后将这些套路做各种排列组合,形成千变万化的武功!

五法

首先补充在Racket中判断一个元素是不是原子的方法实现

(define atom?
  (lambda (x)
    (and (not (pair? x)) (not (null? x)))))

使用效果大致如下

> (atom? 'a)
#t
> (atom? '1)
#t
> (atom? '())
#f
> (atom? '(1 2 3))
#f

car法则

car用于获取一个列表的第一个元素,且只能针对非空列表。不能求一个空列表()的car,不能求一个原子的car

> (car '(1 2 3))
1
> (car '((1 2 3) 2 3))
'(1 2 3)
> (car '())
. . car: contract violation
  expected: pair?
  given: '()
> (car '(1))
1
> (car '(() () ()))
'()

cdr法则

对一个列表,其cdr的结果得到列表扣除car之后的结果,只能针对非空列表

> (cdr '(1 2 3))
'(2 3)
> (cdr '())
. . cdr: contract violation
  expected: pair?
  given: '()
> (cdr '(() () ()))
'(() ())

cons法则

cons添加任意S-表达式到一个列表的开头处

> (cons '1 '())
'(1)
> (cons '1 '(2 3))
'(1 2 3)
> (cons '(1 2) '((), (1 2), 3))
'((1 2) () ,(1 2) ,3)

null?法则

null?用于判断一个列表是否为空。不过在Racket中其也可以作用于一个原子

> (null? '1)
#f
> (null? '())
#t
> (null? '(1 2 3))
#f

eq?法则

eq?用于判断两个原子是否相等。空列表和非空列表分别是一种特例。按照《The Little Schemer》中的说法,每个参数都必须是一个非数字的原子,但是在Racket中实际执行效果会有些出入

> (eq? '1 '1)
#t
> (eq? '() '())
#t
> (eq? '(1 2) '(1 2))
#f

十诫

看完十诫,可以看到Lisp所关注的一个核心词是:递归

第一诫

当对一个原子列表lat进行递归调用时,询问两个有关的问题:(null? lat)和else

当对一个数字n进行递归调用时,询问两个有关的问题:(zero? n)和else

当对一个S-表达式列表l进行递归调用时,询问三个有关l的问题:(null? l)(atom? (car l))和else

第二诫

使用cons来构建列表

第三诫

构建一个列表的时候,描述第一个典型元素,之后cons该元素到一般性递归上

第四诫

在递归时总是改变至少一个参数:

  • 当对一个原子列表lat进行递归调用时使用(cdr lat)
  • 当对数字n进行递归调用时,使用(sub1 n)
  • 当对一个S-表达式l进行递归调用时,只要是(null? l)(atom? (car l))都不为true,那么就同时使用(car l)(cdr l)

在递归时改变的参数,必须向着不断接近结束条件而改变。改变的参数必须在结束条件中得以测试

  • 当使用cdr时,用null?测试是否结束
  • 当使用sub1时,用zero?测试是否结束

第五诫

当用+构建一个值时,总是使用0作为结束代码行上的值,因为加上0不会改变加法的值

当用*构建一个值时,总是使用1作为结束代码行上的值,因为乘以1不会改变乘法的值

当用cons构建一个值时,总是考虑把()作为结束代码行的值

第六诫

简化工作只在功能正确之后开展

第七诫

对具有相同性质的subparts(子部件)进行递归调用:列表的子列表、算术表达式的子表达式

第八诫

使用辅助函数来抽象表示方法

第九诫

用函数来抽象通用模式

第十诫

构建函数,一次收集多个值

参考资料

John McCarthy本来没打算把Lisp设计成编程语言,至少不是我们现在意义上的编程语言。他的原意只是想做一种理论演算,用更简洁的方式定义图灵机

所以,为什么上个世纪50年代的编程语言,到现在还没有过时?简单说,因为这种语言本质上不是一种技术,而是数学。数学是不会过时的。你不应该把Lisp语言与50年代的硬件联系在一起,而是应该把它与快速排序(Quicksort)算法进行类比。这种算法是1960年提出的,至今仍然是最快的通用排序方法




如果本篇文章对您有所帮助,您可以通过微信(左)或支付宝(右)对作者进行打赏!


上一篇     下一篇