计算机程序的构造和解释(SICP) Lec4a 的个人心得

本文分享了《计算机程序的构造和解释》第四讲的心得,重点讨论了引用、按类型分派以及规则匹配的概念。通过规则和模式匹配实现表达式的实例化和求导计算规则的程序表示。文章还介绍了Lisp中表达式匹配和实例化的流程,强调了将复杂问题拆分为小部分解决的重要性。

上节课的博客
https://blog.csdn.net/Changed117/article/details/110727877
视频地址
https://www.bilibili.com/video/BV1Xx41117tr?p=8

上节课主要是讲了引用,通过引用+表结构可以在程序中表达具体的表达式了。

而且上节课写的程序,只要是根据不同的表达式的值,给出不同的化简方式。如果是常量,就做一些事情。如果是变量就做另一些事情。等等

这是一种典型的按类型分派。那么有没有什么办法可以更加清晰的表述这个过程呢?

那么种种如此,他肯定有自己的匹配规则,我们从这个下手,首先。规则究竟是什么?

规则分为好几个部分,左边的部分用于和新的表达式做比较,右边的表达式用于替换原有的表达式。
在这里插入图片描述
所以所有的规则都可以被描述成这样。
我有很多模式,只要匹配上了模式,我就需要一个骨架,根据骨架生成对应的内容,这就是规则在这里插入图片描述

表达式根据模式匹配,匹配上之后根据规则,使用骨架生成新的表达式,生成的过程被称为实例化(教授你让让嘛)
在这里插入图片描述

今天要做的是建立一种语言,用于解释,执行这些规则。
与其吧规则翻译成程序,让计算机理解并执行,不如写个程序让计算机理解这些规则。这就又回到了前几集提到过的。与其解决一种问题,不如写一种语言用于解决一类问题

与其吧每种匹配模式都写出来,比如分数的化简,三角函数。不如将其中共有的表达抽象出来并且拆分实现
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
接下来是求导的计算规则的程序表示(伪代码)
?c ?v 分别表示是对于常量的求导还是对于变量的。
左边是规则。右边是结果。
在这里插入图片描述

这部分表示的就是,?是用于匹配的模式变量,使用?来表示这个表达式需要被模式匹配并转换,而上面这个方框对应的就是下面这个方框的求值结果。他们是相等的。:是用于表示要代换的对象。称为骨架求值。

在这里插入图片描述
下面根据上面的规则做个解释。关于模式匹配
1.一个符号foo与自己匹配,返回它本身
2.如果是一个式子包含符号 f 以及 a 和 b 两个变量,返回一个表结构,第一个个元素是 f 第二个第三个是a,b
3.如果是(? x)的匹配,他可以匹配任何规则,返回一个x
4.(?c x)只能匹配常量。返回一个常量x
5.(?v x)只能匹配变量,返回一个变量x
在这里插入图片描述
语法中可以包含模式变量,语法变量,这样可以通过x作为名字取得其中表达的值

那么到了骨架,根据匹配模式,可以写出这样的骨架用于生成值
1.如果是符号foo,则实例化为它本身
2.如果是一个表f,a,b,则实例化为他们各自实例化后的表,表中的元素为 f 的实例化,a的实例化, b的实例化.
3.而(: x)则实例化为他所匹配的值
在这里插入图片描述

注意这里
(?v v)表示单变量的v 比如(x,y,z)
(? v)表示任意表达式的v 比如(1+x,2+y)
由于这个v出现了两次,所以我们约束他们必须相等才能匹配
在这里插入图片描述

所以现在我们回头来看看这些规则,根据最下方的规则可以得到中间的结果。当然这种规则还可以根据你自己的需求自行添加,这是很自由的。
在这里插入图片描述在这里插入图片描述
接下来给出了lisp自带的化简函数中的一些规则,注意看红框内的部分。这其中的规则和我们上面写的求导规则是一样的
红框内的第一条是0加上任意一条表示返回表达式本身
第二条表示1乘任意表达式e返回e本身
第三条表示0乘任意表达式e返回0

在这里插入图片描述
像这样的规则还有很多。
在这里插入图片描述
在这里插入图片描述
但是我们并不关心规则表达的是什么。我们要写出一种语言用于解释这些规则。这样就可以随意的编写规则了。
那么宏观上来看我们究竟要做什么呢?

就像下图这样,我们的式子先被传入适配器将匹配的值传入词典,根据词典将对应的表达式传入实例化中,实例化根据骨架实例化式子并且组合成新的表达式返回给适配器。适配器继续根据模式匹配表达式并传给实例化,实例化继续根据骨架实例化,直到没有规则可以应用到这个式子上。(一个递归的过程。如果有错误的过程是很有可能无限循环的。)
在这里插入图片描述


更进一步,我们来详细的看一下他是如何工作的
先是匹配器,他是一个盒子,外部输入表达式,模式,以及词典,由词典将模式变量(例如?c v, ?v v)映射到匹配的值上(如果成功匹配,代换表达式中的变量)。处理完成后输出一个新的词典,包含旧词典中的内容以及新词典所匹配成功的内容。
在这里插入图片描述
匹配器的完整代码是一个十分复杂的结构,索性我们目前不需要去探究所有的细节(主要的是视情况分析,不同的分支)
在这里插入图片描述
下面这张图表示了匹配器的宏观结构,传入的参数和上面描述的一样,模式,表达式,和词典
在这里插入图片描述

我们需要做的是同时匹配两棵树,使得表达式的子表达式和模式的子表达式相匹配,举个例子,有如下的模式表达式
在这里插入图片描述
?x表示任意表达式,?y也一样。但是式子中的两个?y必须完全相等才能匹配成功。我们用树形结构表示他就是这样的
在这里插入图片描述
用它去匹配这样一个表达式
在这里插入图片描述
同样给出他的树形结构
在这里插入图片描述
程序需要做的就是遍历两个树形结构,比较他们是否一样,首先遍历前面的结构,他们都是一样的,直到3和?x ,前面说过?x表示可以匹配常量或者变量,都可以。所以模式变量?x = 3被记录到词典中,同理?y=x(这里的x是语法变量,是表达式中的,不是模式变量)同样被记录到词典。直到最后一个?y匹配x,他会从词典中拿?y的值匹配是否相等。相等了,匹配结束,匹配成功。
在这里插入图片描述
这样的过程会重复很多次,多次迭代生成新的词典。
回过头来看程序,这是一个通用过程(不失败情况下一般采取的方式),在lisp中表达式分为左右两部分(lisp中复杂结构都是分为左右部分)首先用传入的词典去匹配左半部分,然后再匹配右半部分。匹配完毕后的词典会被返回到上一部分。
在这里插入图片描述
如果出现失败的情况,他会传递回上级,由上级处理
在这里插入图片描述

接下来是失败条件,如果模式不是原子的而且不是模式变量(模式变量可以表示表达式,肯定不是原子的),但是表达式是原子的(没有小部分),那么肯定是会失败的。
失败了就会进入这里,如果模式和表达式都是原子的,且他们完全一样。那么返回的词典就和之前一样。如果他们都是原子的且不相同,那么就会失败,又或者模式是原子的表达式的复合的,那么也会失败。
在这里插入图片描述
模式变量的匹配是这样处理的。?c x匹配常量,?v x匹配变量,如果匹配成功,就在原来的旧词典基础上添加一个模式和表达式匹配的序对并且返回。当然,在原有词典中记录过的模式变量,如果被再次引用。需要和先前记录的值完全一致才能匹配成功。否则失败(比如?v x之前是x现在变成y了那就会失败)在这里插入图片描述
而实例化器只是需要将传过来的词典,通过骨架生成新的表达式就好了
在这里插入图片描述


接下来该到实例化器了,他所做的其实就是将所有的树形结构遍历对应每一个规则,对于规则对应的,调用规则的骨架生成新的表达式,那么在这其中还需要一个用于化简的化简器,来保证生成出来的式子都是最简的。
这个化简器的思想其实就是将所有的式子想象成简单的对象来处理,每个部分都是一个简单的对象,拆开来处理。他的实际代码是非常复杂的。
在这里插入图片描述
我们从宏观来看,传入一个规则集合,返回一个已经被定义的过程(所谓的化简其实就是根据规则代换然后化简)
在这里插入图片描述
在这里插入图片描述
表达式被传进来,如果式子是最简的,那么就尝试规则,有的规则会拓展式子让他变长,那么就调用简化器对式子的每个部分化简,然后不停的尝试所有的规则。
在这里插入图片描述
化简式子的每个部分就是创建一个新的式子cons,对原来的式子的car和cdr部分分别化简然后组合到一起
在这里插入图片描述
尝试规则的方式是遍历传入的所有规则的集合,找到一个合适的
在这里插入图片描述
总结,一个程序是由很多简单的小部分构成的,哪怕他的整体如何庞大。每个小部分都有对应的作用,将一个程序拆解为小部分理解,会好理解的多。
包括在设计程序的时候,也可以采取这种思维,你想要解决的是哪一类问题,将他们共有的模式抽象出来并且拆解,在细节方面采取分类处理,解决其中不同的地方。这样就可以解决一类问题,而不是一个。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值