详解Clojure的递归(下)——相互递归和trampoline

    详解clojure递归(上)
    详解clojure递归(下)
   
    这篇blog拖到现在才写,如果再不写,估计就只有上篇没有下篇了,趁周末写一下。

    上篇提到了Clojure仅支持有限的TCO,不支持间接的TCO,但是有一类特殊的尾递归clojure是支持,这就是相互递归。且看一个例子,定义两个函数用于判断奇数偶数:

(declare my-odd? my-even?)
(defn my-odd? [n]
      (if (= n 0)
          false
         (my-even? (dec n))))
(defn my-even? [n]
      (if (= n 0)
          true
         (my-odd? (dec n))))

    这里使用declare做前向声明,不然在定义my-odd?的时候my-even?还没有定义,导致出错。可以看到,my-odd?调用了my-even?,而my-even?调用了my-odd?,这是一个相互递归的定义:如果n为0,那肯定不是奇数,否则就判断n-1是不是偶数,反之亦然。

    这个递归定义看起来巧妙,但是刚才已经提到clojure通过recur支持直接的TCO优化,这个相互递归在大数计算上会导致堆栈溢出:

user=> (my-odd? 10000)
java.lang.StackOverflowError (NO_SOURCE_FILE:0)

user=> (my-even? 10000)
java.lang.StackOverflowError (NO_SOURCE_FILE:0)

    怎么解决呢?一个办法是转化成一个直接递归调用,定义一个parity函数,当偶数的时候返回0,当奇数的时候返回1:

(defn parity [n]
      (loop [n n par 0]
            (if (= n 0)
                par
                (recur (dec n) (- 1 par)))))
user=> (parity 3)
1
user=> (parity 100)
0
user=> (parity 10000)
0
user=> (parity 10001)
1   

    parity是直接的尾递归,并且使用了recur优化,重新定义my-odd和my-even就很简单了:

(defn my-even? [n] (= 0 (parity n)))
(defn my-odd?  [n] (= 1 (parity n)))

   
    但是这样的方式终究不是特别优雅,相互递归的定义简洁优雅,有没有什么办法保持这个优雅的定义并且避免堆栈溢出呢?答案是trampoline,翻译过来是弹簧床,查看trampoline函数文档:

user=> (doc trampoline)
-------------------------
clojure.core/trampoline
([f] [f & args])
  trampoline can be used to convert algorithms requiring mutual
  recursion without stack consumption. Calls f with supplied args, if
  any. If f returns a fn, calls that fn with no arguments, and
  continues to repeat, until the return value is not a fn, then
  returns that non-fn value. Note that if you want to return a fn as a
  final value, you must wrap it in some data structure and unpack it
  after trampoline returns.

   简单来说trampoline接收一个函数做参数并调用,如果结果为函数,那么就递归调用函数,如果不是,则返回结果,主要就是为了解决相互递归调用堆栈溢出的问题,查看trampoline的定义:

(defn trampoline
  ([f]
     (let [ret (f)]
       (if (fn? ret)
          (recur ret)
          ret)))

   看到trampoline利用recur做了尾递归优化,原有的my-odd?和my-even?需要做一个小改动,就可以利用trampoline来做递归优化了:

(declare my-odd? my-even?)
(defn my-odd? [n]
      (if (= n 0)
          false
         #(my-even? (dec n))))
(defn my-even? [n]
      (if (= n 0)
          true
         #(my-odd? (dec n))))

 
   唯一的改动就是函数的末尾行前面加了个#,将递归调用修改成返回一个匿名函数了,使用trampoline调用my-even?和my-odd?不会再堆栈溢出了:

user=> (trampoline my-odd? 10000000)
false
user=> (trampoline my-even? 10000)  
true
user=> (trampoline my-even? (* 1000 1000))
true

   文章转自庄周梦蝶  ,原文发布时间 2010-08-22

时间: 2024-07-31 14:41:59

详解Clojure的递归(下)——相互递归和trampoline的相关文章

详解Clojure的递归(上)—— 直接递归及优化

    详解clojure递归(上)     详解clojure递归(下)     递归可以说是LISP的灵魂之一,通过递归可以简洁地描述数学公式.函数调用,Clojure是LISP的方言,同样需要递归来扮演重要作用.递归的价值在于可以让你的思维以what的形式思考,而无需考虑how,你写出来的代码就是数学公式,就是函数的描述,一切显得直观和透明.如果你不习惯递归,那只是因为命令式语言的思维根深蒂固,如x=x+1这样的表达式,从数学的角度来看完全不合法,但是在命令式语言里却是合法的赋值语句.  

详解CentOS 5.8下varnish-2.1.5的安装配置

Varnish是一款强大的反向代理加速软件,关于其工作原理可以参考上图,其具体流程及VCL语法我这里就不做说明,网上资料多,大家还可以对照参考其官方网站和<Varnish中文权威指南>. 一.安装CentOS5.8系统环境下的依耐关系 yum install gcc gcc-c++ yum install automake autoconflibtool ncurses-devel libxslt groff pcre-devel pkgconfig libtool -y 二.下载varnis

oracle10g 手动创建数据详解(linux)下

本文紧跟上篇内容. <oracle10g 手动创建数据详解(linux)上>       http://www.cnblogs.com/fnng/archive/2012/07/19/2600167.html 考虑篇幅过长不易于阅读,所以分个上下两节来进行.这一节中重点解决上一节中第四步与第九步的难题.   设置参数文件与创建数据库命令                                                       如何获得一个的参数文件pfile 呢? 1.问or

以大武汉圈为例详解地方社区线下推广营销策略

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 今天我分享的是地方社区线下推广营销策略,下面内容都是我自己运营大武汉圈(www.dwhquan.com)的实践经验,没有一点深奥的理论,希望大家喜欢和支持! 一.线下活动 做地方网站一定要组织策划活动.我组织活动比较早,自己也爱好旅游户外.06年我网站还没有开始的时候就依托群组织周末市内活动,07年我的网站论坛上线,当时是一个武汉本地的装修论

详解SQL Server 2012下的分析服务

提到SQL Server 2012的分析服务,那么不得不先说下商业智能,它是一个由数据转换成知识的过程.此篇将对SQL Server 2012的分析服务(Analysis Services)以及跟其相关的商业智能做一个简要的介绍,将以一个普通开发人员的角度去阐述和介绍分析服务以及商业智能. 分析服务是SQL Server的一个服务组件.作为一个应用程序开发人员,你已经很熟悉数据库和表,这些在SQL Serer的服务组件中是属于数据库引擎的范畴.还记得你每次打开Management Studio吗

详解Oracle RAC 环境下的连接管理

这篇文章详细介绍了Oracle RAC环境下的连接管理,分别介绍了什么是 Connect Time Load Balancing.Runtime Connection Load Balancing.Connect Time Connection Failover 和 Runtime Connection Failover,以及里面所涉及到的 TAF.ONS.FCF.FAN.LBA 等诸多知识点.本文主要是针对 Oracle RAC 11gR2 环境下的连接管理,但同时也会对比说明一下 Oracl

安卓控件之Button与ImageButton详解以及其按下效果的实现

   Android系统控件Button是一种按钮控件,用户能够在该控件上点击,并后引发相应的事件处理方法:ImageButton用以实现能够显示图像功能的控件按钮.  button的使用十分简单,button的相关属性如:style.android:text .android:gravity . android:layout_weight也无需赘述了.  需要注意一下使用java代码引入资源的时候一般利用setImageResource()方法,将新加入的资源文件如:R.drawable.do

详解ASP.NET MVC下的异步Action的定义和执行原理_实用技巧

Visual Studio提供的Controller创建向导默认为我们创建一个继承自抽象类Controller的Controller类型,这样的Controller只能定义同步Action方法.如果我们需要定义异步Action方法,必须继承抽象类AsyncController.这篇问你讲述两种不同的异步Action的定义方法和底层执行原理. 一.基于线程池的请求处理 ASP.NET通过线程池的机制处理并发的HTTP请求.一个Web应用内部维护着一个线程池,当探测到抵达的针对本应用的请求时,会从池

详解android6.0版本下悬浮窗实现

悬浮窗在安卓中实现起来还是比较容易的,这几天在网上温习了相关资料,运行在我安卓6.0手机上才发现,原来在6.0手机上不是行的. 第一反应肯定是权限相关问题,做了相关处理后,果然让悬浮窗原形毕露了.直接贴代码. public class MainActivity extends AppCompatActivity { private static final int ALERT_WINDOW_PERMISSION_CODE = 100; private Button start_float; @O