热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

红药丸,还是蓝药丸

前一篇《周而复始》讲述了递归函数的周而复始可以作为程序的动力源。Murphis拿出两颗药丸,一颗红色,一颗蓝色,让Neo选。吃了红药丸&#
前一篇《周而复始》讲述了递归函数的周而复始可以作为程序的动力源。

Murphis 拿出两颗药丸,一颗红色,一颗蓝色,让 Neo 选。吃了红药丸,可以觉醒,看到真相;吃了蓝药丸,则可以继续浑噩地生活下去。

这样的程序,用 Emacs Lisp 该怎么写?

(defun murphis (答案)(cond((string= 答案 "红药丸") '红药丸)((string= 答案 "蓝药丸") '蓝药丸)(t (message "没有第三种选择!"))))(murphis "红药丸")

用 Emacs 打开 init.el 文件,将上面的代码复制到 init.el 文件的尾部,然后将光标移动到 murphis 这个函数定义的末尾,执行 C-x C-e。这样 Emacs Lisp 解释器就知道了 murphis 这个函数的定义了。

接下来,(murphis "红药丸") 是对 murphis 函数进行求值。在《黑客帝国》里,等同于 Neo 对 Murphis 说,我要红药丸!倘若将光标移到这个表达式的末尾,然后执行 C-x C-e,结果就会在微缓冲区里显现 红药丸

在 Emacs Lisp 里,上述的 cond 表达式,就是所谓的条件表达式。

cond 表达式的形式如下:

(cond(<谓词 1> <表达式 1>)(<谓词 2> <表达式 2>)... ... ...(<谓词 n> <表达式 n>))

什么是谓词&#xff1f;上文代码中的 (string&#61; 答案 "红药丸") 就是谓词&#xff08;Predicate&#xff09;。

简单地说&#xff0c;谓词就是一个求值结果为真&#xff08;t&#xff09;或假&#xff08;nil&#xff09;的表达式。像 (string&#61; 答案 "红药丸") 这样的表达式&#xff0c;string&#61; 是一个函数&#xff0c;它会判断自己所接受的两个字符串原子是否相同&#xff0c;倘若相同&#xff0c;则结果为真&#xff0c;否则为假。

tnil 本身也能构成谓词。因为在 Emacs Lisp 里&#xff0c;表达式不一定必须是以 ( 开头&#xff0c;以 ) 结尾的语句。Emacs Lisp 的原子本身就可以构成表达式。

在上述代码中&#xff0c;&#39;红药丸&#39;蓝药丸 以及 "没有第三种选择&#xff01;"&#xff0c;它们都是原子&#xff0c;但也都是表达式。只要是表达式&#xff0c;Emacs Lisp 解释器就可以对它们进行求值&#xff0c;求值结果就是原子本身。

这里需要解释一下&#xff0c;&#39;红药丸&#39;蓝药丸 都是符号原子。之所以要以单引号 &#39; 作为前缀&#xff0c;是为了避免 Emacs Lisp 将它们视为函数或参数&#xff08;变量&#xff09;。不妨将符号原子视为标签&#xff0c;把它贴到函数上&#xff0c;它就是代表函数。把它贴到函数的参数&#xff08;变量&#xff09;上&#xff0c;它就代表参数。当这个标签的前面放上 &#39; 时&#xff0c;它就是一张未贴任何事物上的标签。用同样的办法&#xff0c;可以避免 Emacs Lisp 将列表视为函数求值表达式。例如&#xff0c;&#39;(list 1 2 3)&#xff0c;在 Emacs Lisp 解释器看来&#xff0c;它是由 list 这个符号原子与三个数字原子构成的列表&#xff0c;而不是 (list 1 2 3) 的求值结果 (1 2 3)

为什么要将 (string&#61; 答案 "红药丸") 这样的表达式称为谓词呢&#xff1f;倘若将 string&#61; 理解为 is&#xff0c;那么这个表达式的含义就变成了 is 答案 "红药丸"&#xff0c;在这句话的后面再加上个问号的话&#xff0c;就变成了 is 答案 "红药丸"&#xff1f;。基于初中英语知识&#xff0c;可以发现这是个英文与汉语夹杂的一般疑问句。所以……差不多就这意思。

在上述的 cond 表达式中&#xff0c;对谓词的求值顺序是从上而下&#xff0c;直到某个谓词的求值结果为真&#xff08;即 t&#xff09;&#xff0c;便对该谓词所关联的表达式进行求值&#xff0c;所得结果即为 cond 表达式的求值结果。

倘若一个 cond 表达式中所有谓词的求值结果皆为假&#xff08;nil&#xff09;&#xff0c;那么这个 cond 表达式的结果就是未定义。不过 Emacs Lisp 在遇到 cond 表达式的求值结果为未定义的时候&#xff0c;会以 nil 作为求值结果。

例如&#xff0c;

(defun murphis (答案)(cond((string&#61; 答案 "红药丸") &#39;红药丸)((string&#61; 答案 "蓝药丸") &#39;蓝药丸)))(murphis "黑药丸")

(murphis "黑药丸") 的求值结果就是 nil

有的时候&#xff0c;只存在两种选择&#xff0c;非此即彼。像这样的选择&#xff0c;用 cond 表达式来模拟&#xff0c;有点繁琐&#xff0c;这时可以考虑用 如果...就...否则... 这样的表达式。例如&#xff1a;

(defun 齿轮 (m n)(if (

在推敲这段代码之前&#xff0c;先看一下 if 表达式的结构&#xff1a;

(if <谓词> <表达式 1> <表达式 2>)

这个结构可以这样解读&#xff0c;如果 <谓词> 为真&#xff0c;就对 <表达式 1> 求值&#xff0c;否则就对 <表达式 2> 求值。

将这个语法结构套到 齿轮 函数里的 if 表达式&#xff0c;结果是&#xff0c;如果 ( 的求值结果为真&#xff0c;那么就对表达式 (progn (insert (format "%d-" m)) (齿轮 (&#43; m 1) n)) 进行求值&#xff0c;否则就对 (insert (format "%d\n" m)) 进行求值。这几个较长的表达式看上去有点复杂&#xff0c;需要有点耐心&#xff0c;逐层进行拆解。

首先来看 progn 表达式&#xff0c;它的作用是让 Emacs Lisp 解释器依序对一组并列的表达式进行求值&#xff1a;

(progn<表达式 1><表达式 2>... ... ...<表达式 n>)

由于 progn 本身也是一个表达式。按照 Emacs Lisp 解释器的法律&#xff0c;每个表达式必须有一个值。因此&#xff0c;progn 表达式会将它所囊括的这组表达式中的最后一个表达式的求值结果作为自身的结果。

实际上&#xff0c;list 表达式也能产生类似 progn 的效用。例如&#xff1a;

(list<表达式 1><表达式 2>... ... ...<表达式 n>)

Emacs Lisp 解释器会对 list 囊括的这组表达式依序求值。只不过 list 返回的是这组表达式的全部的求值结果&#xff0c;亦即列表。

insert 函数&#xff0c;我们已经见识过多次了。虽然一直没对它做太多介绍&#xff0c;但是通过实践&#xff0c;不难发现&#xff0c;它可以在缓冲区里光标所在的位置插入文本——字符串原子。

format 函数&#xff0c;它可以用于构造字符串原子&#xff0c;只不过是以格式化的方式。所谓格式化&#xff0c;就相当于……做语文填空题。例如&#xff0c;(format "%d-" m)&#xff0c;就相当于在 "( )-" 这句话的括号内填写一个数字。倘若你填 1&#xff0c;那么这句话就变成了 "1-"。同理 (format "%d\n" m))&#xff0c;倘若填上 10&#xff0c;结果就是 "10\n"。在字符串原子中&#xff0c;\n 这个字符表示换行&#xff0c;相当于你在文本编辑器里输入一行文本之后&#xff0c;单击了一次回车键。

还有一个很简单的表达式&#xff0c;(&#43; m 1)&#xff0c;它的意思是 m &#43; 1&#43; 是数字原子的运算符&#xff0c;它也是一个函数。类似的还有 -*/<>&#61;>&#61;<&#61;。虽然这些运算符的用法不太符合我们在小学数学里学到的运算方法&#xff0c;但有的时候它也很有用。例如&#xff0c;你去超市买了许多东西&#xff0c;回家后想核对一下钱数&#xff0c;可以这样算 (&#43; 2.5 8.75 9 10 20 35.5)

弄懂了这些琐碎的函数的含义&#xff0c;齿轮 函数的定义差不多可以看懂了吧&#xff1f;这个函数最难看懂的部分其实是它在内部对自身进行求值——递归求值。

还记得那个 c-malloc 函数吗&#xff1f;

(defun c-malloc (name type n)(interactive(list (read-string "变量名称&#xff1f;")(read-string "类型&#xff1f;")(read-string "数量&#xff1f;")))(insert (format "%s *%s &#61; malloc(%s * sizeof(%s));" type name n type)))

当我依序回答这个函数问我的三个问题之后&#xff0c;它就会在缓冲区里显现&#xff1a;

double *foo &#61; malloc(n * sizeof(double));

这个函数有个问题&#xff0c;当 n 为 1 时&#xff0c;它会给出&#xff1a;

double *foo &#61; malloc(1 * sizeof(double));

在 C 语言中&#xff0c;1 * sizeof(double) 表示拿 1 去乘一个数。小学生都知道&#xff0c;拿 1 去乘一个数就等于那个数本身。因此 c-malloc 若想表现自己已经小学毕业了&#xff0c;那么对于这样的情况&#xff0c;它应该给出&#xff1a;

double *foo &#61; malloc(sizeof(double));

为此&#xff0c;需要对 c-malloc 略作改进&#xff1a;

(defun c-malloc (name type n)(interactive(list (read-string "变量名称&#xff1f;")(read-string "类型&#xff1f;")(read-string "数量&#xff1f;")))(if (string&#61; n "1")(insert (format "%s *%s &#61; malloc(sizeof(%s));" type name type))(insert (format "%s *%s &#61; malloc(%s * sizeof(%s));" type name n type))))

以上&#xff0c;就是 Emacs Lisp 语言中常用的条件表达式的用法。在程序中&#xff0c;我们可以用它们来模拟「选择」。

一些热衷于从哲学层面探究人生意义的成功人士喜欢说这样的话&#xff0c;人生的意义&#xff0c;就在于你的选择&#xff01;

他们若是略微懂一点编程&#xff0c;就会发现这样的话有些愚蠢。倘若人生的意义真的是在于选择&#xff0c;这就是变相地承认自己活在一个编制好的程序里。任何选择&#xff0c;在这个程序里&#xff0c;不过是一个谓词&#xff0c;而与这个谓词相关联的表达式早已在那里了。也就是说&#xff0c;无论你怎么选择&#xff0c;你的人生早已确定。因此&#xff0c;说人生的意义在于选择&#xff0c;无异于说自己相信天命所归&#xff0c;这就是所谓的宿命论。倘若一切都是程序安排好的结果&#xff0c;那么你选与不选&#xff0c;本质上并没有区别&#xff0c;你并没有权力不选择什么……

在我看来&#xff0c;人生的意义至少在于 debug。无论怎么看&#xff0c;去消除这个世界的 bug&#xff0c;总比被这个世界逼迫着整天作各种选择更有意义。

下一篇&#xff1a;周游抑或毁灭世界



推荐阅读
  • PHP中元素的计量单位是什么? ... [详细]
  • PHP中处理回车换行符转换的有效方法与技巧
    PHP中处理回车换行符转换的有效方法与技巧 ... [详细]
  • 在稀疏直接法视觉里程计中,通过优化特征点并采用基于光度误差最小化的灰度图像线性插值技术,提高了定位精度。该方法通过对空间点的非齐次和齐次表示进行处理,利用RGB-D传感器获取的3D坐标信息,在两帧图像之间实现精确匹配,有效减少了光度误差,提升了系统的鲁棒性和稳定性。 ... [详细]
  • C#编程指南:实现列表与WPF数据网格的高效绑定方法 ... [详细]
  • Java服务问题快速定位与解决策略全面指南 ... [详细]
  • 在进行网络编程时,准确获取本地主机的IP地址是一项基本但重要的任务。Winsock作为20世纪90年代初由Microsoft与多家公司共同制定的Windows平台网络编程接口,为开发者提供了一套高效且易用的工具。通过Winsock,开发者可以轻松实现网络通信功能,并准确获取本地主机的IP地址,从而确保应用程序在网络环境中的稳定运行。此外,了解Winsock的工作原理及其API函数的使用方法,有助于提高开发效率和代码质量。 ... [详细]
  • 本文详细探讨了Java集合框架的使用方法及其性能特点。首先,通过关系图展示了集合接口之间的层次结构,如`Collection`接口作为对象集合的基础,其下分为`List`、`Set`和`Queue`等子接口。其中,`List`接口支持按插入顺序保存元素且允许重复,而`Set`接口则确保元素唯一性。此外,文章还深入分析了不同集合类在实际应用中的性能表现,为开发者选择合适的集合类型提供了参考依据。 ... [详细]
  • Spring Boot 实战(一):基础的CRUD操作详解
    在《Spring Boot 实战(一)》中,详细介绍了基础的CRUD操作,涵盖创建、读取、更新和删除等核心功能,适合初学者快速掌握Spring Boot框架的应用开发技巧。 ... [详细]
  • 本文探讨了如何在C#中实现USB条形码扫描仪的数据读取,并自动过滤掉键盘输入,即使不知道设备的供应商ID(VID)和产品ID(PID)。通过详细的技术指导和代码示例,展示了如何高效地处理条形码数据,确保系统能够准确识别并忽略来自键盘的干扰信号。该方法适用于多种USB条形码扫描仪,无需额外配置设备信息。 ... [详细]
  • 开发心得:深入探讨Servlet、Dubbo与MyBatis中的责任链模式应用
    开发心得:深入探讨Servlet、Dubbo与MyBatis中的责任链模式应用 ... [详细]
  • 本文将详细介绍在Android应用中添加自定义返回按钮的方法,帮助开发者更好地理解和实现这一功能。通过具体的代码示例和步骤说明,本文旨在为初学者提供清晰的指导,确保他们在开发过程中能够顺利集成返回按钮,提升用户体验。 ... [详细]
  • 本项目在Java Maven框架下,利用POI库实现了Excel数据的高效导入与导出功能。通过优化数据处理流程,提升了数据操作的性能和稳定性。项目已发布至GitHub,当前最新版本为0.0.5。该项目不仅适用于小型应用,也可扩展用于大型企业级系统,提供了灵活的数据管理解决方案。GitHub地址:https://github.com/83945105/holygrail,Maven坐标:`com.github.83945105:holygrail:0.0.5`。 ... [详细]
  • 本题库精选了Java核心知识点的练习题,旨在帮助学习者巩固和检验对Java理论基础的掌握。其中,选择题部分涵盖了访问控制权限等关键概念,例如,Java语言中仅允许子类或同一包内的类访问的访问权限为protected。此外,题库还包括其他重要知识点,如异常处理、多线程、集合框架等,全面覆盖Java编程的核心内容。 ... [详细]
  • 本文详细探讨了C语言中`extern`关键字的简易编译方法,并深入解析了预编译、`static`和`extern`的综合应用。通过具体的代码示例,介绍了如何在不同的文件之间共享变量和函数声明,以及这些关键字在编译过程中的作用和影响。文章还讨论了预编译过程中宏定义的使用,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • MySQL性能优化与调参指南【数据库管理】
    本文详细探讨了MySQL数据库的性能优化与参数调整技巧,旨在帮助数据库管理员和开发人员提升系统的运行效率。内容涵盖索引优化、查询优化、配置参数调整等方面,结合实际案例进行深入分析,提供实用的操作建议。此外,还介绍了常见的性能监控工具和方法,助力读者全面掌握MySQL性能优化的核心技能。 ... [详细]
author-avatar
神的禁卫军
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有