博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
在 NewLisp 实现匿名函数的递归
阅读量:7043 次
发布时间:2019-06-28

本文共 3176 字,大约阅读时间需要 10 分钟。

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

(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))))    (lambda      fargs      (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))))    (lambda      fargs      (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> thisnil> (setf this 100)100> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 10)3628800> this100
  • 勿轻易尝试Lisp的宏,更不要轻易尝试NewLisp的宏,前者你会受伤,后者你能体会到和查C++模板错误一样的过程,甚至更加爆炸。

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

文章转载自 开源中国社区 [

你可能感兴趣的文章
iOS开发网络篇—HTTP协议
查看>>
Open×××的Linux内核版,鬼魅的残缺 part II:The encrypt engine
查看>>
让您在Directx3D中轻松获取剩余显存
查看>>
根据表的某个字段拆分大表成多个小表,以前做的,留作笔记
查看>>
apk逆向工程工具
查看>>
Linux阵列 RAID详解
查看>>
linux运维前景与运维人员最佳职业规划录像
查看>>
MySQL数据库企业级应用实战(Linux运维必学) 套餐上线了。
查看>>
Centos7上卸载MariaDB数据库,安装mysql
查看>>
我的友情链接
查看>>
linux压缩&解压&归档
查看>>
Linux文件与目录管理命令总结
查看>>
我的友情链接
查看>>
rsync+inotify-tools
查看>>
Powershell v3 来下载vmare vForum 2013虚拟大会收藏资料
查看>>
sersync+rsync数据同步
查看>>
我的友情链接
查看>>
表示数值的字符串
查看>>
我的友情链接
查看>>
【云快讯】之二十六《AWS推出网络文件存储EFS,进军NAS存储市场》
查看>>