当前位置: 首页 > 编程日记 > 正文

在NewLisp中实现匿名函数的递归

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

匿名函数在很多语言中的表现形式大概如下:

(lambda (n)(* (+ n 1) (- n 1)))

只有参数列表和函数体,而没有名字。在大部分情况下没问题,但是一旦需要用到递归的话,就有点麻烦了,因为不知道如何去递归的调用一个匿名函数。


在学术界中有一些解决这个问题的办法,其中一个就是Y组合子,但是那个太繁琐,而且难以通过宏自动将一个lambda变成可递归形式,没什么好处。

根据历史经验,目前比较好的办法,就是实现一个操作符,匿名函数通过这个操作符来调用自身:

(lambda (n) ... (this (- n 1))) 或者是 (lambda (n) ... (lambda (- n 1)))

第一种是用this或其他东西来表示当前匿名函数本身,直接调用就可以递归。第二种是和有名函数一样,用和定义匿名函数一样的操作符来调用自身。

然而第二种不实际,因为这样会造成混乱,比如需要嵌套lambda时,而且其语义也不对。

所以此文主要围绕第一种方式:实现让this指向当前匿名函数,从而可以递归调用自身。


NewLisp是一个Lisp语言的实现,也可以说是一个方言,其与Common Lisp相比,少了很多东西,但远比Common Lisp容易使用。Lisp系列的语言有一个特点:没有语法。或者说极小语法,用Lisp编写程序,直接没有了语法阶段,从语义开始起步,所以非常接近编译器。Lisp是一个多范式编程语言,支持命令式、函数式、面向对象等等,当然Lisp其实应该被称为:列表处理语言。或者说是:抽象语法树处理语言。源程序就是AST,通过设计AST来构建软件。

首先我们找个最简单的办法,然后逐步改善。

在匿名函数内部定义一个局部函数,通过调用这个函数,就可以实现递归自身了。比如在要定义的递归匿名函数中再定义一个函数作为主体函数,通过调用这个函数来实现递归的效果。

大概的形式如下:

(lambda (n)(define this (n) (if (< n 2) 1 (* n (this (- n 1)))))(this n))

现在可以设计一个定义可递归匿名函数的宏了:

(define-macro(lambda* _args)(letex ((fargs _args) (fbody (cons 'begin $args))(fcall (cons 'this (flat _args))))(lambdafargs(define (this fargs) fbody)fcall)))

这样,只需要用lambda*,就可以定义一个可递归的匿名函数:

; 通过this来调用自身
(lambda* (n) (if (< n 2) 1 (* n (this (- n 1)))))

但是这样有一个很大的问题,这个定义的函数会污染名称空间,而且不同的lambda*会覆盖掉this,因为define是在全局中定义的。在Common Lisp中,可以通过定义仅在函数内部可见的嵌套函数来解决:

(defmacro re-lambda (&rest body)`(lambda (&rest args)(labels (,(cons 'this body))(apply #'this args))))

但是在NewLisp中,我没有找到如何定义仅在函数内部可见的嵌套函数,所以还需要通过其他的办法才能解决。


于是我想到了自动生成函数名的方式,NewLisp有一个函数(time-of-day),返回一个以较为精确的时间戳:

> (time-of-day)
66359119.140
> (time-of-day)
66359415.039

这样我们可以在扩展宏时自动生成一个函数名,以避免冲突:

 (define-macro(lambda* _args)(let ((f (string "$."(time-of-day)))) ;生成一个随时间变化的函数名(eval(list'define(cons (sym f) (flat _args))(list'let(list (list 'this (sym f))) ; this指向这个函数(cons 'begin $args))))))

于是当用lambda*定义时,会自动生成一个每个时刻都不同的名称,比如$.66789291.992、$.66922513.671,几乎不会有重复。


但是这样依然不行,因为只是避免的名称冲突,但还是会污染名称空间,而且在某些情况下依然会造成难以预料的问题。所以在最后,设计了一个终极版本的lambda*,没有任何负面作用。


终极版本

(define-macro(lambda* _args)(letex ((fargs _args) (fbody (cons 'begin $args))(fcall (cons 'this (flat _args))))(lambdafargs(let ((this (lambda fargs fbody)))fcall))))

通过在扩展宏时,在lambda内部在定义一个lambda作为主体函数,使用let将局部变量this指向这个主体函数,于是就可以通过this来模拟调用自身了,函数不需要有名字,而只有一个指向函数的局部变量this,效果非常好:

; 阶乘函数
(lambda* (n)(if (< n 2)1(* n (this (- n 1))))); 宏展开后如下=>
(lambda (n)(let ((this(lambda (n)(begin(if (< n 2)1(* n (this (- n 1))))))))(this n)))

一段测试结果:
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 1)
1
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 2)
2
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 3)
6
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 4)
24
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 5)
120
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 20)
2432902008176640000
> this
nil
> (setf this 100)
100
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 10)
3628800
> this
100


* 勿轻易尝试Lisp的宏,更不要轻易尝试NewLisp的宏,前者你会受伤,后者你能体会到和查C++模板错误一样的过程,甚至更加爆炸。

用农业界的一个术语来说:就像一颗原子弹。

转载于:https://my.oschina.net/u/1982890/blog/469153

相关文章:

C# Obsolete(已弃用方法属性)

class Realization : Interface{/// <summary>/// 已弃用的方法&#xff0c;Obsolete第二个参数设置为true,调用此方法会产生警告并引起编译器报错/// </summary>/// <param name"skey">参数</param>/// <returns></returns>[Ob…

如何训练2457亿参数量的中文巨量模型“源1.0”

如何训练2457亿参数量的中文巨量模型“源1.0” 浪潮人工智能研究院 从2018年的BERT到2020年的GPT-3&#xff0c;NLP语言模型经历了爆发式的发展过程&#xff0c;其中BERT模型的参数量为3.4亿&#xff0c;而GPT-3的模型参数量达到了1750亿。2021年9月&#xff0c;浪潮人工智能…

Linux驱动程序编写

工作需要写了我们公司一块网卡的Linux驱动程序。经历一个从无到有的过程&#xff0c;深感技术交流的重要。Linux作为挑战微 软垄断的强有力武器&#xff0c;日益受到大家的喜爱。真希望她能在中国迅速成长。把程序文档贴出来&#xff0c;希望和大家探讨Linux技术和应用&#xf…

PHP+socket+SMTP、POP3协议发送、接收邮件

1、实现SMTP协议的类dsmtp.cls.php&#xff1a;<?php//通过socket实现SMTP协议的功能//version: 1.1//author : DCC//create : 2014-01-17 23:38:24//modify : 2014-01-18 16:59:04//more : http://www.thinkful.cn/archives/389.htmlclass Dmail_smtp{var $socket;var $…

JavaScript学习记录 (三) 函数和对象

1.函数使用 function 关键字来声明函数函数的命名规则和变量一样JS没有函数签名&#xff0c;所以没有函数重载JS函数中的所有参数都是值传递&#xff1b;不能通过引用传递// 定义函数 function test(arg) {return arg 10; } // 定义一个同名函数 function test(arg, arg1) {re…

基于jQuery图片自适应排列显示代码

基于jQuery图片自适应排列显示代码。这是一款基于jquery.flex-images插件实现的类似谷歌图片流效果。效果图如下&#xff1a; 在线预览 源码下载 实现的代码。 html代码&#xff1a; <div style"max-width:900px;margin:auto;padding:0 10px"><h3>演示…

计算机史最疯狂一幕:豪赌50亿美元,“蓝色巨人”奋身一跃

作者 | OneFlow社区来源 | OneFlow社区“Go big or go home. ”是美国人的一句习语&#xff0c;经常会在赛场上听到&#xff0c;NBA球迷应该很熟悉&#xff0c;翻译过来就是“要不变强大&#xff0c;要不滚回家”。在1960年初期的计算机行业&#xff0c;IBM正站在这样一个十字路…

CentOS Linux内核升级全过程

首先说明&#xff0c;下面带#号的行都是要输入的命令行&#xff0c;且本文提到的所有命令行都在终端里输入。接下来&#xff0c;让我们一起开始精彩的Linux内核升级之旅吧&#xff01;一、准备工作启动Linux系统&#xff0c;并用根用户登录&#xff0c;进入终端模式下。1、查看…

Windows程序设计------字体不等宽引出的问题及其细节知识

在写Windows程序设计的Typer程序时&#xff0c;我并不是在每一个使用HDC的地方都重新创建选中字体&#xff0c;而是在一开始选中之后&#xff0c;就没有再删除它&#xff0c;代码如图&#xff1a; 结果我的字体不是等宽字体&#xff01; 起先我以为是没有设置WM_INPUTLANGCHANG…

看闯关东原来知道古代已经十六进制了

闯关东第四集中夏掌柜说黄县口诀什么意思 1625 2125 31875 425 53125 6375 74375 85 95625 1625 116875 1275 138125 14875 159375 161 这个问题实际上是过去商品流通中的一种算法。过去的衡器十六两为一斤&#xff0c;也就是十六进制。为了计算方便&#xff0c;先人便选用了这…

手机客户端和web端开发的异同

2019独角兽企业重金招聘Python工程师标准>>> 版本升级。用户角度上看&#xff0c;客户端升级必须让用户手动下载整个新的安装包覆盖安装&#xff0c;而web的升级无需用户做任何事情。开发角度上看&#xff0c;如果客户端有个小bug需要紧急修复&#xff0c;需要修复完…

AI 监视打工人,这个国家明确说:保护我方“摸鱼权”!

‍‍撰文 | 刘芳来源 | 学术头条近日&#xff0c;国美控股集团对员工“摸鱼” 的处罚引起了关于职场中隐私权的巨大争议。事实上&#xff0c;这一事件并非个例。如今人工智能&#xff08;AI&#xff09;算法在职场中的使用&#xff0c;已经涉及到包含隐私权在内的诸多问题。随着…

Hibernate复习之Hibernate基本介绍

众所周知。眼下流行的面向对象的对象关系映射的Java持久层框架有MyBatis和Hibernate。他们都是对象关系映射ORM。 解决的主要问题就是对象-关系的映射。域模型和关系模型都分别建立在概念模型的基础上。域模型是面向对象的&#xff0c;关系模型是面向关系的&#xff0c;普通情况…

Linux常用命令手册

核心&#xff1a; cat定位&#xff0c;sed时间搜索&#xff0c;grep关键字查询&#xff0c;tail行数&#xff0c;|管道结合 最前N行 这个主要看文件最开始是什么时候记录了什么 #head -1 XXX.log 最后N行 #cat XXX.log | tail -n 10 时间区间搜索 #sed -n /2010-05-20…

程序员敲诈老板,或面临 37 年监禁

‍‍作者 | 祝涛 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;12月1日&#xff0c;网络设备制造商优比快&#xff08;Ubiquiti&#xff09;的前雇员尼古拉斯夏普&#xff08;nicholas Sharp&#xff09;被捕&#xff0c;他被控窃取数据&#xff0c;并试图…

使用modernizr.js检测浏览器对html5以及css3的支持情况

使用modernizr.js检测浏览器对html5和css3的支持情况 详情请看主页&#xff1a;modernizr主页 1. modernizr 是什么&#xff1f; modernize 是一个js库————一个用于检测当前浏览器对html5&css3 的支持情况&#xff0c;其中包括对 css3 的 font-face、border-radius、…

SharePoint运行状况分析器有关磁盘空间不足的警告

对于负责管理SharePoint内部部署安装的SharePoint管理员&#xff0c;SharePoint Health Analyzer是一款出色的工具。此功能不仅有助于解决服务器故障和服务失败的问题&#xff0c;还提供了有关如何解决问题的提示。总的来说&#xff0c;我觉得这个功能非常有帮助。但是&#xf…

百度PHP高级顾问惠新宸:PHP在百度的发展历程

惠新宸&#xff0c;百度PHP高级顾问,年二十有八&#xff0c;好追根究底&#xff0c;有不良嗜好, 幸性本善。乙酉年识互联网&#xff0c;丁亥年入雅虎&#xff0c;翌年入百度。虽性好安稳&#xff0c;然经变无数&#xff0c;唯常叹"人生&#xff0c;菠菜汤尔"。大家好…

Python 远程连接服务器用它就够了

作者 | 费弗里来源 | Python大数据分析❝本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes❞1 简介日常工作中经常需要通过SSH连接到多台远程服务器来完成各种任务&#xff0c;当需要操作的服务器众多&#xff0c;且要执行的任务涉…

如何在centos安装python-mysql

在python中使用mysql&#xff0c;需要安装mysql-python依赖包&#xff0c;可以通过pip来安装&#xff1a;pip install MySQL-python如果发生错误&#xff0c;需要先安装一个开发包&#xff1a;yum install python-devel如果还是报错&#xff0c;运行&#xff1a;yum install my…

DNS 到底怎么工作的? (How does dns work?)

其实这个问题每次看的时候都觉得很明白&#xff0c;但是很久之后就忘记了&#xff0c;所以这次准备记录下来。深入到这个过程的各个细节之中&#xff0c;以后多看看。 Step 1 请求缓存信息&#xff1a; 当你在开始访问一个 www.baidu.com 开始&#xff0c;第一件事情就是去访问…

#pragma pack(n) 的作用

在C语言中&#xff0c;结构是一种复合数据类型&#xff0c;其构成元素既可以是基本数据类型&#xff08;如int、long、float等&#xff09;的变量&#xff0c;也可以是一些复合数据类型&#xff08;如数组、结构、联合等&#xff09;的数据单元。在结构中&#xff0c;编译器为结…

LoadRunner设置检查点的几种方法介绍

LoadRunner设置检查点的几种方法介绍 发布时间: 2011-5-03 11:53 作者: 一米阳光做测试 来源: 51Testing软件测试网采编 字体: 小 中 大 | 上一篇 下一篇 | 打印 | 我要投稿 3、将脚本切换回代码界面&#xff0c; 在光标闪烁的上行&#xff0c;添加如下的代码&…

Python 爬虫利器 Selenium 从入门到进阶

作者 | 俊欣来源 | 关于数据分析与可视化今天小编就来讲讲selenium&#xff0c;我们大致会讲这些内容selenium简介与安装页面元素的定位浏览器的控制鼠标的控制键盘的控制设置元素的等待获取cookies调用JavaScriptselenium进阶selenium的简介与安装selenium是最广泛使用的开源W…

获取下个月的今天

/* 获取下个月的今天如果 $date 2018-1-31 二月没有31号 则获取二月份的最后一天 返回值为2018-2-28如果 $date 2018-1-15 返回值为2018-2-15 -- psw-- */function getNextMonthDays($date){$firstday date(Y-m-01, strtotime($date));$lastday strtotime("$firstday …

C语言的sizeof和strlen

strlen是函数&#xff0c;而sizeof是算符。strlen需要进行一次函数调用&#xff0c;而对于sizeof而言&#xff0c;因为缓冲区已经用已知字符串进行了初始化&#xff0c;起长度是固定的&#xff0c;所以sizeof在编译时计算缓冲区的长度。因为sizeof()测试的是数组的长度。而strl…

机器人 Ameca「苏醒」瞬间逼真到令人恐惧,网友纷纷惊叹……

整理 | 禾木木 出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09; 近日&#xff0c;国内外网友都被一段机器人「苏醒」的视频惊讶到。 视频开始时&#xff0c;机器人似乎已经睡着&#xff0c;眼睛闭着&#xff0c;头部略微向下倾斜。随着肩膀的伸展&#xff0c;机器…

linux源码包卸载方式

linux源码包软件的安装与卸载3人收藏此文章,我要收藏 发表于1年前 , 已有593次阅读 共0个评论Linux软件安装与卸载&#xff08;源码包形式&#xff09;&#xff1a;一般情况下linux程序的发布不能像windows那样&#xff0c;直接打包成一个setup.exe文件&#xff0c;然用户安装 …

在实践中我遇到stompjs, websocket和nginx的问题与总结

阅读原文&#xff1a;https://wdd.js.org/stomp-over... 1. AWS EC2 不支持WebSocket 直达解决方案 英文版 简单说一下思路&#xff1a;WebSocket底层基于TCP协议的&#xff0c;如果你的服务器基于HTTP协议暴露80端口&#xff0c;那WebSocket肯定无法连接。你只要将HTTP协议修改…

C语言memset函数详解(Linux下和windows下的差异)

memest原型 (please type "man memset" in your shell) void *memset(void *s, int c, size_t n); memset:作用是在一段内存块中填充某个给定的值&#xff0c;它对较大的结构体或数组进行清零操作的一种最快方法。 #include <stdio.h>#include <string.…