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

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

(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
> this
nil
> (setf this 100)
100
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 10)
3628800
> this
100


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

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

时间: 2024-08-01 16:51:58

在NewLisp中实现匿名函数的递归的相关文章

转:php中的匿名函数和闭包(closure)

一:匿名函数 (在php5.3.0 或以上才能使用) php中的匿名函数(Anonymous functions), 也叫闭包函数(closures), 允许指定一个没有名称的函数.最常用的就是回调函数的参数值.(http://php.net/manual/zh/functions.anonymous.php) 匿名函数的定义: $closureFunc = function(){ .... }; eg: 把匿名函数赋值给变量,通过变量来调用 $closureFunc = function($s

JavaScript中的匿名函数及函数的闭包以及作用域

1. ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

java学习中,匿名函数、构造方法、构造代码块、构造方法中调用构造方法(java 学习中的小记录)

java学习中,匿名函数.构造方法.构造代码块.构造方法中调用构造方法(java 学习中的小记录) 作者:王可利(Star·星星) 匿名函数 匿名对象:没有名字的对象 匿名对象使用的注意点: 1.一般不会用匿名对象给属性赋值,无法获取属性值,每次new 都是一个新的对象. 2.匿名对象永远都不可能是一个对象. 如:person new().name = "星星":是不行的 匿名对象的好处:书写简单. 匿名对象使用的场景: 1.如果一个对象调用一个方法一次的时候,就可以用匿名对象来调用.

jquery&#39;中的匿名函数

  //jquery'中的匿名函数 (function(){ alert("this is a test"); })(); //和这个基于jQuery的比较下: $(function(){ alert("this is a test"); }); jquery'中的匿名函数

php中的匿名函数和闭包(closure)

一:匿名函数 (在php5.3.0 或以上才能使用) php中的匿名函数(Anonymous functions), 也叫闭包函数(closures), 允许指定一个没有名称的函数.最常用的就是回调函数的参数值.(http://php.net/manual/zh/functions.anonymous.php) 匿名函数的定义: $closureFunc = function(){ .... }; eg: 把匿名函数赋值给变量,通过变量来调用 $closureFunc = function($s

day23 内置函数,匿名函数,递归

Python之路,Day11 = Python基础11 内置函数divmod(x, y)   # (商, 模)enumerate(可迭代对象)     # (序号,值)eval(字符串) # 把字符串当成命令执行set({1,2,3})   # 可变集合(增删改)frozenset({1,2,3})        # 不可变集合globals()   # 查看全局变量locals()   # 查看局部变量isinstance(3, int)     # 查看3是不是int类型pow(3,3)  

内置函数,匿名函数,递归

内置函数: 详查下网址 https://docs.python.org/3/library/functions.html?highlight=built#ascii divmod(x, y)   # (商, 模)enumerate(可迭代对象)     # (序号,值)eval(字符串) # 把字符串当成命令执行frozenset({1,2,3})        # 不可变集合globals()   # 查看全局变量locals()   # 查看局部变量isinstance(3, int)  

Python基础(10)_内置函数、匿名函数、递归

一.内置函数 1.数学运算类 abs:求数值的绝对值 divmod:返回两个数值的商和余数,可用于计算页面数 >>> divmod(5,2) (2, 1) max:返回可迭代对象中的元素中的最大值或者所有参数的最大值 语法:max(iterable,key,default) 1 salaries={ 2 'egon':3000, 3 'alex':100000000, 4 'wupeiqi':10000, 5 'yuanhao':2000 6 } 7 8 print(max(salari

【Python之匿名函数及递归】

一.匿名函数及内置函数补充 1.语法 Python使用lambda关键字创造匿名函数.所谓匿名,意即不再使用def语句这样标准的形式定义一个函数. 语法: lambda [arg1[, arg2, ... argN]]: expression 例: 普通函数 def func(x,y): return x+y print(func) print(func(1,2)) 输出 <function func at 0x102b31f28> 3 等价的匿名函数 #匿名函数 f=lambda x,y:x