lisp入门一Word文档格式.docx

上传人:b****4 文档编号:8162416 上传时间:2023-05-10 格式:DOCX 页数:11 大小:25.63KB
下载 相关 举报
lisp入门一Word文档格式.docx_第1页
第1页 / 共11页
lisp入门一Word文档格式.docx_第2页
第2页 / 共11页
lisp入门一Word文档格式.docx_第3页
第3页 / 共11页
lisp入门一Word文档格式.docx_第4页
第4页 / 共11页
lisp入门一Word文档格式.docx_第5页
第5页 / 共11页
lisp入门一Word文档格式.docx_第6页
第6页 / 共11页
lisp入门一Word文档格式.docx_第7页
第7页 / 共11页
lisp入门一Word文档格式.docx_第8页
第8页 / 共11页
lisp入门一Word文档格式.docx_第9页
第9页 / 共11页
lisp入门一Word文档格式.docx_第10页
第10页 / 共11页
lisp入门一Word文档格式.docx_第11页
第11页 / 共11页
亲,该文档总共11页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

lisp入门一Word文档格式.docx

《lisp入门一Word文档格式.docx》由会员分享,可在线阅读,更多相关《lisp入门一Word文档格式.docx(11页珍藏版)》请在冰点文库上搜索。

lisp入门一Word文档格式.docx

a))

反之一个被引用的表仅仅被视为表

(atom'

引用看上去有些奇怪,因为你很难在其它语言中找到类似的概念,但正是这一特征构成了lisp最为与众不同的特点——代码和数据使用相同的结构来表示,而我们用quote来区分它们。

(eqxy)当x和y的值相同或者同为空表时返回t,否则返回空表()

(eq'

a'

b)

()'

下一章,我们将讲解与表相关的操作符和条件操作符,以及lisp程序的基本元素——函数。

2006-4-1618:

15cad

lisp入门

(二)

上一集我们讲了lisp世界七个公理的前三个,这一集我们接着讲剩下的四个。

首先是三个表操作

(carx)要求x是一个表,它返回x中的第一个元素,例如:

(car'

(ab))

a

(cdrx)同样要求x是一个表,它返回x中除第一个元素之外的所有元素组成的表,例如:

(cdr'

(bc)

(consxy)要求y是一个表,它返回一个表,这个表的第一个元素是x,其后是y中的所有元素,例如:

(cons'

(bc))

(abc)

a(cons'

b(cons'

c())))

看到这里大家可能会问,为什么没有取表中除开头外其它某个位置上的元素的操作符,别急,等我们讲到地球人都知道的函数和递归你就知道该怎么办了,也许你现在已经想得差不多了?

接下来要介绍给大家的是构成程序逻辑的一个基本功能……条件分支,在lisp中,它是由cond操作符完成的,cond是七个公理中最后一个也是形式最复杂的一个(欧几里德的最后一个公理也如是):

(cond(p1e1)(p2e2)...(pnen))

p1到pn为条件,e1到en为结果,cond操作符依次对p1到pn求值,直到找到第一个值为原子t(还记得吗?

)的p,此时把对应的e作为整个表达式的值返回,例如:

(cond((eq'

b)'

first)

 

((atom'

a)'

second))

second

好了,至此我们已经有了lisp世界的所有基本公理,我们可以开始构建整个世界的规则了。

在这七个操作符中,除quote和cond之外,以其他的五个操作符开头的表达式总是要对它的所有自变量求值,然后产生结果,我们把这样的表达式叫做函数。

lisp入门(三)

上一集我们讲到了“函数”,其实这个概念早在初中数学里就已经学过了,一个函数无非就是将自变量映射到值的对应关系,在lisp里也一样。

lisp中的函数定义我们已经在上节给出(快速抢答:

谁还记得请举手),在lisp中采用如下形式描述一个函数:

(lambda(p1p2...pn)e)

其中,pi为原子,在函数中称之为参数,e是表达式,也就是函数体。

调用一个函数的方式如下:

((lambda(p1p2...pn)e)a1a2...an)

其中ai为表达式,按照我们的惯例,称之为实参。

整个函数的调用过程如下:

每一个表达式ai(实参)先求值,然后再将这些实参代入e中求值,最后的结果即为整个表达式的返回值。

如果一个表达式的第一个元素是一个原子,但不是基本操作符(也就是我们先前提到的那7个),如:

(fa1a2...an)

并且f的值是一个函数(lambda(p1p2...pn)e),则上述表达式等价于

看了这一段,可能大家都有点晕,到窗口去做几个深呼吸,然后回来做下面这个练习,看看这个表达式的结果是什么?

((lambda(f)(f'

(bc)))'

(lambda(x)(cons'

ax)))

如果你得出了结果,那么继续往下看,否则再把前面几段话多读几遍,把上面的练习输入到一个能自动匹配括号的文本编辑器里继续研究。

在这里我打算插几句题外话,可能有很多人已经见识过这个lambda了,不过不太可能是在lisp里(要是这样的话你就应该不用来看这片“入门”了,不是吗?

),而多半是在python里,python手册中对这个lambda仅仅是一笔带过,他大概是这么说的:

“使用lambda这个词是因为它类似于lisp语言里同名的一个语法结构。

”好了,我们现在就来看看lambda这个典故的真正起源。

lambda这个词来源于lambda演算理论。

lambda是什么?

对于现在的人来说,这个概念不过就是“函数”而已,但是对于lambda演算理论的出现的那个年代来说,它可是一种革命性的创新。

lambda演算理论过于复杂,而且作为一篇lisp的简介,讨论它已经完全偏离了主题,但是它所提出的另一个概念——高阶函数(highorderfunction)——却在lisp语言中占有重要的地位,甚至可以说是lisp如此与众不同的主要原因。

正如“高阶导数”就是“导数的导数”一样,所谓高阶函数,其实就是“函数的函数”(高数老师,原谅我吧)。

即把一个函数本身当作另一个函数的自变量(在现代的c++中提出的“functor”这个概念其实就是高阶函数在c++中的一种实现)。

高阶函数的出现,将“函数”在编程语言中的地位提升到一个“一等公民”的地位,你可以像操作任何基本数据类型一样操作一个函数,对它进行变换、传递,随你怎么折腾。

下面我们回到正题,继续讨论lisp中的函数,我们可以看到,至今为止,我们的函数都还没有名字,函数可以没有名字,也就是匿名函数正是lisp的另一大特色,lisp可以让程序员把数据和名字剥离开,这对于许多其它的编程语言来说是直到现在也无法享受到的一种奢侈。

函数没有名字会带来一个问题,那就是你无法在函数中调用自身(好啦,我知道还有y组合,不过这是一篇入门文章),所以lisp提供了一种形式可以让你用一个标识符来引用函数:

(labelf(lambda(p1p2...pn)e))

这个表达式和前面的简单lambda表达式等价,但是在e中出现的所有f都会被替换为整个lambda表达式,也就是递归。

同时,lisp为它提供了一种简写形式:

(defunf(p1p2...pn)e)

你可以开始写你的第一个有用的lisp程序了,你打算写什么?

(无论什么,只要不是helloworld就好)

下一集,我们将给出一些常用的函数。

16cad

lisp入门(四)

lisp的语法元素在前几集中已经基本讨论完毕,相比c#或java数百页的specification,它可能简单的让你有些惊讶,不过,伟大的东西总是简单的,不是吗?

现在让我们来回顾一下上一集中提到的内容,首先提几个问题:

既然cond在概念上相当于过程式语言中的if语句,那么与if相对的else分支在cond表达式中应该如何描述?

在(我们已经学过的)lisp中如何表达“重复”这个语义?

或者你能写一个foreach循环函数?

(注:

不要问输入输出函数或算术逻辑运算在哪儿之类的问题,它们都是微不足道的事……)

这一集中,我们将描述几个常用的函数,并给出它们的简单实现

首先解答在第一集中提出的问题:

如何取一个表中的第二个、第三个或第n个元素?

可能有些读者已经想到了,取第二个元素可以采用如下形式:

(car(cdrx))

同理,取第三个元素是这样的:

(car(cdr(cdrx)))

事实上,这种组合在lisp中经常要用到,为了方便,lisp提供了一个通用模式——cxr,其中x为a或d的序列,来简记car和cdr的组合,例如:

(cadr'

((ab)(cd)e))

(cd)

(caddr'

e

(cdar'

(b)

另外,使用(liste1e2...en)来表示

(conse1(conse2(...(consen'

())...)))

c'

())))

(list'

b'

c)

现在我们定义一些新的常用函数,我建议你先自己想一想,不要急着看我给出的实现。

某些函数在commonlisp中已经存在,所以如果你想试验一下,给它们换个名字)

(nullx),测试x是否为空表。

例如:

(null'

t

(andxy),逻辑与,当且仅当x和y都不是空表时返回'

t,否则返回空表。

(and'

(and(atom'

a)(eq'

c))

()

(notx),逻辑非,当x是空表时返回'

(有人问我or在哪儿?

)例如:

(not'

(not(eq'

b))

(appendxy),连接两个表x和y,注意它与cons和list之间的不同之处。

(append'

(ab)'

(cd))

(abcd)

(xy))

(xy)

(pairxy),这里x和y是两个长度相同的表,pair生成一个表,其中每个元素是x和y中相应位置上的元素组成的一个元素对,这个函数的返回值类似于其它语言中的map或dictionary的概念。

(pair'

(abc)'

(xyz))

((ax)(by)(cz))

(assocxy),其中x是一个原子,y是一个形如pair所返回的表,assoc在y中查找第一个左元素为x的元素对并返回。

(assoc'

((ax)(by)))

x

((a(foobar))(by)(cz)))

(foobar)

(substxyz),在表z中将任意层次上出现的原子y都替换为表达式x。

(subst'

(xy)'

(ab(abc)d))

(a(xy)(a(xy)c)d)

下面我们给出这些常用函数的简单实现:

(defunnull(x)

(eqx'

()))

(defunand(xy)

(cond(x(cond(y'

t)('

t'

('

())))

(defunnot(x)

(cond(x'

t)))

(defunappend(xy)

(cond((nullx)y)

t(cons(carx)(append(cdrx)y)))))

(defunpair(xy)

(cond((and(nullx)(nully))'

((and(not(atomx))(not(atomy)))

(cons(list(carx)(cary))

(pair(cdr)(cdry))))))

(defunassoc(xy)

(cond((eq(caary)x)(cadary))

t(assocx(cdry)))))

(defunsubst(xyz)

(cond((atomz)

(cond((eqzy)x)

tz)))

t(cons(substxy(carz))

(substxy(cdrz))))))

如果看到这里你还没有晕菜,说明你的神经的确很坚强。

注意在这些例子中是如何表达“重复”这个概念的,在lisp中,最常用的重复其实并不是真正意义上的重复,而是递归,这也是绝大多数函数式语言的一个共同特征——函数的嵌套和递归,构成了整个程序逻辑。

这一部分内容可以让你真正感受到lisp的特色,与编写过程式语言的程序相比,编写lisp程序需要一种完全不同的思维方式,也许这正是lisp语言几十年来长盛不衰的真正原因吧。

理解了这一部分,下一集中我们将领教一下lisp的威力,我们将用lisp编写一个lisp解释器。

如果你以前没有见过这个程序,我保证它一定会让你吃惊。

17cad

lisp入门(五)

上一集我们已经见到了一个lisp程序的大致外貌,在文末,我提到这一集中我们将会用lisp写一个lisp解释器,事实上这个解释器并不太长,虽然它有可能是你至今为止见过的最长的一个。

我已经有点等不及了,让我们先来看一下整个程序,然后再来讲解:

(defuneval(ea)

(cond

((atome)(assocea))

((atom(care))

((eq(care)'

quote)(cadre))

atom) 

(atom 

(eval(cadre)a)))

eq) 

(eq 

(eval(cadre)a)

(eval(caddre)a)))

car) 

(car 

cdr) 

(cdr 

cons) 

(cons 

cond) 

(evcon(cdre)a))

t(eval(cons(assoc(care)a)

(cdre))

a))))

((eq(caare)'

label)

(eval(cons(caddare)(cdre))

(cons(list(cadare)(care))a)))

lambda)

(eval(caddare)

(append(pair(cadare)(evlis(cdr 

e)a))

a)))))

(defunevcon(ca)

(cond((eval(caarc)a)

(eval(cadarc)a))

t(evcon(cdrc)a))))

(defunevlis(ma)

(cond((nullm)'

t(cons(eval 

(carm)a)

(evlis(cdrm)a)))))

可能有的读者已经发现,lisp并不要求你一定要在使用函数前先定义它)

整个程序包含三个函数,主函数我们遵从lisp(和python、perl)的惯例,叫它eval,它是整个程序的骨架。

eval的定义比我们以前看到的任何一个函数都要长,让我们考虑它的每一部分是如何工作的。

eval有两个自变量:

e是要求值的表达式,a是由一些赋给原子的值构成的表,这些值有点象函数调用中的参数。

这个形如pair返回值的表叫做上下文,正是为了构造和搜索这种表我们才在前一章写了pair和assoc。

eval的骨架是一个有四个子句的cond表达式,如何对表达式求值取决于它的类型,第一个分支处理原子,如果e是原子,我们在上下文中寻找它的值:

(eval'

x'

((xa)(yb)))a

第二个分支是另一个cond,它处理形如(a)的表达式,其中a是原子。

这包括所有的基本操作符,每个对应一条分支。

(eq'

())t>

(consx'

(bc))'

((xa)(yb)))(abc)

这几个分支(除了quote)都调用eval来寻找自变量的值。

最后两个分支更复杂些。

为了求cond表达式的值我们调用了一个叫evcon的辅助函数。

它递归地对cond分支进行求值,寻找第一个元素返回t的子句,如果找到了这样的子句,它返回此分支的第二个元素。

(cond((atomx)'

atom)('

list))'

((x'

(ab))))list

第二个分支的最后部分处理函数调用。

它把原子替换为它的值(应该是lambda或label表达式)。

然后对所得结果表达式求值。

于是:

(eval'

(f'

((f(lambda(x)(cons'

ax)))))

变为:

((lambda(x)(cons'

ax))'

它返回(abc)

eval的最后两个cond分支处理第一个元素是lambda或label的函数调。

用为了对label表达式求值,先把函数名和函数本身压入上下文,然后调用eval对一个内部有lambda的表达式求值,即:

((labelfirstatom(lambda(x)(cond((atomx)x)('

t(firstatom(carx))))))y)'

((y((ab)(cd)))))

变为

((lambda(x)(cond((atomx)x)('

t(firstatom(carx)))))y)'

((firstatom(labelfirstatom(lambda(x)(cond((atomx)x)('

t(firstatom(carx)))))))(y((ab)(cd)))))

最终返回a。

最后,对形如((lambda(p1p2...pn)e)a1a2...an)的表达式求值,先调用evlis来求得自变量(a1a2...an)对应的值(v1v2...vn),把(p1v1)(p2v2)...(pnvn)添加到上下文里,然后对e求值。

((lambda(xy)(consx(cdry)))'

(bcd))'

(consx(cdry))'

((xa)(y(bcd))))

最终返回(acd)。

讲了这么一大篇,如果你看懂了,说明你已经理解lisp甚至fp的基本编程方式和思路,那么我们写了一个如此之长的程序究竟能干什么呢?

我们在这里得到了一个非常优美的计算模型,eval函数实际上实现了整个语言,用它我们可以定义所需的任何其它函数。

换句话说,我们现在有了一个自己的lisp。

由此可见,递归下降的语法分析是多么美好啊,因为它意味着你可以用几十、最多不过一两百行程序搞定一个复杂的分析器

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 工程科技

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2