CLisp 20:理解On-Lisp书中的延续continuation

来源:互联网 发布:电脑风水罗盘软件 编辑:程序博客网 时间:2024/05/09 06:42

什么是延续?在Windows、Linu等操作系统中,延续就是一个挂起的线程,包括线程挂起时的各种状态,CPU寄存器、堆栈、程序指针,可以恢复执行。在这里,程序是线形执行的,再加上各种跳转,函数返回也可看成跳转。
在LISP语言中,一切都变得不同,这里没有程序指针,执行过程也不是线形的。只有函数、Lambda函数,延续也要用函数来表述。例如(* (+ 1 2) 3)),执行完加法后挂起了,挂起前可以用val保存加法结果,恢复时执行(* val 3)即可。LISP程序不能在任意时刻挂起,只能在执行完某个S表达式后挂起。LISP的延续可以恢复执行很多次,延续就像一个函数,接受一个参数(参数表示挂起前S表达式的结果),恢复一个延续,就像调用一个函数一样,而且可以输入不同的参数。

但是,一个程序不是一个简单的S表达式,上面例子变个形式,增加几层函数调用。
(defun f()  (* (+ 1 2) 3)
(defun g()  (print f())
执行加法后挂起,延续不再是(* val 3),因为执行(* val 3)后不能回到函数g执行print语句。虽然(print (* val 3))看起来像延续,但这种构造延续的方式是不可取的。正确的构造如下(On-Lisp书中使用的构造方法),先用通俗的表达方式

延续 = (g (* val 3))
g = #'(lambda (x) (print x))
下面是正式的表达方式
(defun g()
  (let ((pg #'(lambda (x) (print x))))
       (f pg)))
(defun f(parent)
  (parent (* (+ 1 2) 3)))
执行加法后的延续为(lambda (val) (parent (* val 3)))。
可以看到,这里重新安排了函数调用、返回过程,函数调用时将父函数对象传给子函数,子函数在返回时调用父函数的实现主体,用这种方式解决“执行(* val 3)后不能返回

到函数g”的问题。可以这样理解,整个程序由一个个的小块组成,每块都是不可中断的,块与块之间是可以中断的。在正常的调用返回方式中,所有块被组织成一棵树;为了构造延续,我们将树变成了穿线树,即另外用一根线将所有节点串起来,每个块都指向了下一个要执行的块,每个块的最后一条语句都是执行下一个块。

 

 

         On-Lisp书中实现的延续,对函数编写方式进行了限制,只允许写成下面形式:

(defun fun-name (args)   (multiple-value-bind (vars)   value-form   body  )))

就是先执行 vars = value-form,再执行函数体body,只允许在执行value-form后挂起。

即使用这么简单的形式,也能将程序组织成一棵树,例如下面例子

(defun f ()

  (bind (v) (g) (f1)))

(defun f1 ()

  (bind (v) (h) (f2)))

(defun g ()

  (bind (v) (k) (g1)))

+-----   +                     +-----+      +-----+

 |  f     +---------------------+  g  +------+  k  |

 +--+--+                     +--+--+      +-----+

     |                               |

     |                               |

 +--+--+        +--+--+      +--+--+

 | f1    |-------  |  h    |      |  g1   |

 +--+--+        +----- +     +-----  +

     |

     |

 +--+--+

 | f2    |

 +----- +

上面例子中,执行顺序为k、g、g1、f、h、f1、f2。为了将各函数串起来,函数k需要知道g,函数k返回时调用g的body部分;函数g1需要知道f,函数g1返回时调用f的body部分;函数h需要知道f1,函数h返回时调用f1的body部分。为了做到这一点,将函数f传给g,再由g传给g1;将函数f1传给h;将函数g传给k。对原函数定义进行改造,如下所示:

(defun  f  (preturn)

  (let   ((pbody  #’(lambda (v) (f1 preturn))))

        (g pbody)))

(defun  f1  (preturn)

  (let   ((pbody  #’(lambda (v) (f2 preturn))))

        (h pbody)))

(defun  g  (preturn)

  (let   ((pbody  #’(lambda (v) (g1 preturn))))

        (k pbody)))

     对函数f2hg1k的返回语句进行改造,原返回语句为(xxx),则改为(preturn (xxx)),即将返回值作为参数调用下一个要执行的函数。调用函数f时,传入函数#’values,这是系统自带的函数,什么都不做。

      像上面样子写函数,肯定会累死人,会有一大堆笔误,需要用宏来解决问题。

 

 

首先,要用宏=defun来定义函数,自动在前面加一个参数preturn,实现起来很简单

(defmacro =defun (name parms &rest body)

  `(defun ,name (preturn ,@parms) ,@body))

接着改造调用函数的代码,例如原语句为(fname x y z),变成(fname preturn x y z),需要定义一个宏fname

(defmacro fname (&rest parms)

  `(fname preturn ,@parms))

我们不能为每个函数定义一个上面的宏,太麻烦了。宏=defun中能定义一个函数,也能定义一个宏,修改宏=defun的定义,注意必须用progn将两条语句组合成一个表达式,否则只有最后一句生效。在这里,defmacro的表现像函数,只返回最后一个表达式的值。

(defmacro =defun (name parms &rest body)

`(progn

(defun    ,name (preturn ,@parms) ,@body)

      (defmacro ,name ,parms  `(,',name preturn ,,@parms))))

      上面定义导致函数和宏重名,后面的定义会冲掉前面的定义,我们必须给函数名加个前缀,例如加一个等号。还有,为了能处理递归的函数,应该先定义宏,后定义函数。例如 (defun foo (x) (* (foo (- x 1) x))),应该变成

      (defmacro foo (x) `(=foo preturn x))

      (defun   =foo (preturn x) (* (foo (- x 1) x)))如果先定义函数后定义宏,函数体里面的foo就是未定义符号。

      修改=defun的定义,得到On-Lisp书上的版本,concatenate函数拼接字符串,intern函数将字符串变成合法的标识符。

(defmacro =defun (name parms &rest body)

  (let ((f (intern (concatenate 'string "=" (symbol-name name)))))

    `(progn

       (defmacro ,name   ,parms         `(,',f preturn ,,@parms))

       (defun   ,f   (preturn ,@parms)   ,@body))))

      前面树状调用关系的例子,可以用宏=defun改造各函数的定义,例如函数f改成下面样子

(defun  f  (preturn)                              ==>  (=defun f ()

  (let   ((pbody  #'(lambda (v) (f1 preturn))))   ==>    (let ((pbody #'(lambda (v) (f1))))

        (g pbody)))                               ==>         (g)))

      上面最后一行有个明显的错误,宏(g)展开为(=g preturn),使用的是f的参数preturn,而不是let语句的参数pbody。如果把pbody改名为preturn,一个函数中出现两个preturn参数,怎么理清楚使用关系。let定义的参数会覆盖外面环境的同名参数,其覆盖的范围不包括参数列表,仅限于参数列表后面的语句。这样就清楚了,(f1 return)在let的参数列表中,使用函数f的参数preturn;(g preturn)在let的参数列表后面,使用的是let定义的参数preturn。

(defun  f  (preturn)                                ==>  (=defun f ()

  (let   ((preturn  #'(lambda (v) (f1 preturn))))   ==>    (let ((preturn #'(lambda (v) (f1))))

        (g preturn)))                               ==>         (g)))

 

 

      现在来看宏=bind,它的作用就是把下面左边的代码简写成右边的形式。

(=defun f()                               ==>   (=defun f ()

  (let ((preturn #'(lambda (v) (f1))))    ==>      (=bind (v) (g)

(g)))                                ==>                (f1)))

      把上面例子中的v变成parms,把(g)变成expr,把(f1)变成body,就能得到宏=bind的实现。

(defmacro =bind (parms expr &rest body)

  `(let ((preturn #'(lambda ,parms ,@body)))

        ,expr))

 

 

 

原创粉丝点击