10.3.2 利用连续的代码

来源:互联网 发布:淘宝评价管理网址 编辑:程序博客网 时间:2024/05/12 22:50

10.3.2 利用连续的代码

 

问题是,我们既希望做尾递归调用,还要在尾递归调用完成后,再执行一些代码。这看起来是一个棘手的问题,但有一个有趣的解决方案。我们把要在递归调用完成后执行的所有代码,拿来作为参数值,提供给递归调用,这样,我们要写的函数将仅只包含一个递归调用。

可以把这个看作是另一种类型的累加器参数:我们不是累加值,而是累加“需要在以后运行的代码”。现在的问题是,我们如何能拿到其余的代码,并把它作为给函数的参数值?这可能要感谢一等函数(first class functions),这个最后的参数就称为连续(continuation),因为它指定运行应该如何继续。

在看过一些实际的例子后,这一切会变得更清楚。清单 10.17 是一个简单的函数,首先,以通常的风格实现,然后,使用连续。这里,我们将使用 C#,只有一个新的概念要理解,但请记住,C# 不支持尾递归:在 C# 中,这个技术不能作为递归的优化而使用。(在 C# 中连续仍然可用,只是不是为递归的。)

 

清单 10.17 用连续写代码(C#)

// Reports result as return value 

int StringLength(string s) {

  returns.Length; 

void AddLengths() {     [1]

  int x1= StringLength("One");

  int x2= StringLength("Two"); 

  Console.WriteLine(x1+ x2); 

}

 

// Reports result using continuations 

void StringLengthCont(string s,Action<int> cont) { 

 cont(s.Length);     [2]

void AddLengthsCont() { 

 StringLengthCont("One", x1 =>     [3]

    StringLengthCont("Two", x2 =>    [4]

      Console.WriteLine(x1 + x2) 

  )); 

}

 

在两个版本中,我们首先声明了计算字符串长度的函数。按通常的编程风格,把结果作为返回值;当使用连续时,我们增加了一个函数(连续)作为最后一个参数值。要返回结果,StringLengthCont 函数调用这个连续[2]。我们将使用函数代替通常的 return 语句,这样,值是作为给函数的参数值 ,而不是把它作为结果存储在栈中。

下一个函数,AddLengths,[1]计算两个字符串的长度,再把这两个值加起来,再输出结果。在使用连续的版本中,只包括一个顶层对StringLengthCont 函数的调用[3]。调用的第一个参数值是字符串,第二个参数值是连续。顶层的调用是函数要做的最后事情,因此,在 F# 中,可以使用尾调用运行,它不会占用任何栈空间。

连续接收第一个字符串的长度作为参数值;在内部,再为第二个字符串调用StringLengthCont。另外,把连续作为最后参数值给它,它只被调用一次,我们可以计算两个长度的和,并输出结果。在 F# 中,在连续内部的调用[4]还是尾调用,因为它是代码在 lambda 函数中所要做的最后工作。现在,我们要看一下使用这种编程风格,来优化我们以前的函数,计算树中元素的和。

 

0 0
原创粉丝点击