什么是延续?在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)))
对函数f2、h、g1、k的返回语句进行改造,原返回语句为(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))