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

匿名函数在不少语言中的表现形式大概以下:编程

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

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


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

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

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

第一种是用this或其余东西来表示当前匿名函数自己,直接调用就能够递归。第二种是和有名函数同样,用和定义匿名函数同样的操做符来调用自身。this

然而第二种不实际,由于这样会形成混乱,好比须要嵌套lambda时,并且其语义也不对。spa

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


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

首先咱们找个最简单的办法,而后逐步改善。code

在匿名函数内部定义一个局部函数,经过调用这个函数,就能够实现递归自身了。好比在要定义的递归匿名函数中再定义一个函数做为主体函数,经过调用这个函数来实现递归的效果。

大概的形式以下:

(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.99二、$.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++模板错误同样的过程,甚至更加爆炸。

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

相关文章
相关标签/搜索