cps全称叫continuation passing style,简要来讲就是告诉函数下一步做什么的递归方式,由于普通递归有栈溢出的问题,而cps都是尾递归(tail recursion),尾递归则是没有栈溢出问题的,所以haskell推荐都用cps的方式去编写代码。

当然,相对于普通递归方式,cps也有着非常不便于理解的问题。

def fact(n):
if (n==0):
return 1
else:
return n* fact(n-1)
print fact(400)

这是一段递归求阶乘的python代码。用cps改写就是

def fact_cps(n,ret):
if (n==0):
ret(1)
else:
fact_cps(n-1, lambda x:ret(n*x))
def ret2(x):
print x
fact_cps(400,ret2)

这里要注意的是,我们在传参数的时候多了一个ret,而这个ret其实是一个函数,函数的作用就是传入一个值并打印他。在实际运行过程中,其实是这样子的

以fact_cps(3,ret2)求3的阶乘为例,第一步我们是执行fact_cps(3,ret2),到第五行的地方,我们执行的是fact_cps(2, lambda x:ret2(3*x)),这里的ret即最外层的ret2

fact_cps中的lambda x:ret (3*x)其实是一个函数的简单写法,也就是说这是一个输入x的函数,然后运算ret(3*x)

第二步调用以后,fact_cps的输入值是2以及一个叫做lambda x:ret(3*x)的函数,也就是说ret函数变成了ret(x): print (3*x) 然后到第五行调用fact_cps(1,lambda x:ret(2*x))即fact_cps(1,lambda x:ret2(3*2*x))

第三布调用的就是fact_cps(0,lambda x:ret2(3*2*1*x)),然后到第三行结束,所以总的一个计算其实就是lambda x:ret2(3*2*1*x)然后将最后一次调用的1带入,得到6。

这中间我们可以看到,和普通递归传递的是参数不同,cps调用每一步都产生一个新的函数,传递给下一步调用,这个传递的函数告诉了被递归函数下一步做的是什么。

用haskell写cps更难懂点先写一个简单点的

mysqrt :: Floating a => a -> a
mysqrt a = sqrt a
print (mysqrt 4) :: IO ()

这是一个普通的开根号,用cps写则是

mysqrtCPS :: a -> (a -> r) -> r
mysqrtCPS a k = k (sqrt a)
mysqrtCPS 4 print :: IO ()

这里把print函数传递进了mysqrtCPS并且用print函数调用sqrt a的结果,达到了cps的目的

普通的阶乘是这么写的

fact :: (Num a, Eq a)=>a->a
fact 0=1
fact n=n*(fact (n-1))
print (fact 40)
815915283247897734345611269596115894272000000000

cps方式是这么写的

factCPS :: (Num a, Eq a)=>a->(a->IO ())->IO ()
factCPS 0 ret=ret 1
factCPS n ret=factCPS (n-1) (\x->ret (n*x))
factCPS 40 print :: IO ()
815915283247897734345611269596115894272000000000

对于Fibonacci数列,普通的方法是这么写的

fib :: (Num a,Eq a)=>a->a
fib 0=0
fib 1=1
fib n=fib (n-1)+fib (n-2)
print (fib 30)
832040

CPS方式的Fibonacci数列是这么写的

fibCPS :: (Num a, Eq a)=>a->(a->IO ())->IO ()
fibCPS 0 f=f 0
fibCPS 1 f=f 1
fibCPS n f=fibCPS (n-1) (\f1->fibCPS (n-2) (\f2->f (f1+f2)))
fibCPS 30 print :: IO ()
832040

参考

http://www.haskell.org/haskellwiki/Continuation

http://www.haskell.org/haskellwiki/Tail_recursion

http://en.wikibooks.org/wiki/Haskell/Continuation_passing_style

http://people.csail.mit.edu/meyer/6001/continuations.txt

haskell中的cps的更多相关文章

  1. haskell中的monad

    monad本意是单子.在haskell中,第一个接触的基本都是IO action,通过把IO动作包装起来我们能很方便的与现实世界进行数据交换.但其实monad的用途不止如此,monad还能讲一系列操作 ...

  2. haskell中的do

    在haskell中,有一个do的语句专门用来做一些不那么“干净”的事情,比如读写都需要用do来开头 一开始以为do的作用是做monad,后来发现是错误的,其实do做的事情是包裹一个顺序操作 比如在如下 ...

  3. haskell中的let和where

    haskell中有两种定义局部变量的方法let和where,方法分别如下 roots a b c = ((-b + det) / (a2), (-b - det) / (a2)) *a*c) a2 = ...

  4. Haskell中cabal install glib遇到的问题

    1. 运行命令cabal install glib时出现错误: Cannot find gtk2hsC2hs Please install `gtk2hs-buildtools` first and ...

  5. Haskell语言学习笔记(30)MonadCont, Cont, ContT

    MonadCont 类型类 class Monad m => MonadCont m where callCC :: ((a -> m b) -> m a) -> m a in ...

  6. Haskell 笔记 ③

    ①循环?NO!请递归思考问题! 手艹一个求列表中最大值代码,C语言中习惯性for扫一下比较出最大值.但是可以用递归! maximum'::(Ord a)=>[a]->a maximum' ...

  7. haskell io模块

    haskell中的io模块主要是用于读写文件屏幕的,通过import IO来导入 其中有如下常用定义 data IOMode = ReadMode | WriteMode | AppendMode | ...

  8. Haskell 与范畴论

    说到 Haskell,这真是一门逼格极高的编程语言,一般初学者如果没有相关函数式编程的经验,入门直接接触那些稀奇古怪的概念,简直要跪下.现在回想起来,隐隐觉得初学者所拥有的命令式编程语言(impera ...

  9. JavaScript中的Partial Application和Currying

    这篇文章是一篇学习笔记,记录我在JS学习中的一个知识点及我对它的理解,知识点和技巧本身并不是我原创的.(引用或参考到的文章来源在文末) 先不解释Partial Application(偏函数应用)和C ...

随机推荐

  1. 网站接入QQ登录的两种方法

    第一种使用系统自带按钮登录,但是这样会弹出新窗口,不喜欢 //调用QC.Login方法,指定btnId参数将按钮绑定在容器节点中 QC.Login({ //btnId:插入按钮的节点id,必选 btn ...

  2. 在django中使用自定义标签实现分页功能

    效果演示: github地址:https://github.com/mncu/django_projects/tree/master/django_projects/pagination_test 本 ...

  3. sql按字段值进行统计

    用group by 如有个student表里有性别sex来统计 select sex,count(*) from student group by sex;

  4. Windows 7 下如何设置机器级别的DCOM权限

    Windows 7 下如何设置机器级别的DCOM权限 To grant Remote Activation permissions to the SMS Admins group From the S ...

  5. nodejs总结

    1.locomotive Powerful MVC web framework for Node.js. https://github.com/jaredhanson/locomotive 是基于ex ...

  6. HDU 3775 Chain Code ——(Pick定理)

    Pick定理运用在整点围城的面积,有以下公式:S围 = S内(线内部的整点个数)+ S线(线上整点的个数)/2 - 1.在这题上,我们可以用叉乘计算S围,题意要求的答案应该是S内+S线.那么我们进行推 ...

  7. Android_helloworld

    看见 HelloWorld 相信你就明白我要干什么了.这是每个程序的第一步,让 HelloWorld 带我们走入 Android 的学习之旅吧. 前面我们下载了海马模拟器,所以接下来我们是在模拟器上进 ...

  8. (引用) unittest测试驱动之执行测试(三)

    转载:http://www.wtoutiao.com/p/ydeoyY.html 在unittest的模块中,提供了TestRunner类来进行运行测试用例,在实际的应用中,经常使用的是TextTes ...

  9. springMVC 实现ajax跨域请求

    普通的ajax请求是无法跨域的! 如: <html> <head> <script src="http://code.jquery.com/jquery-1.1 ...

  10. hihoCoder 1305 区间求差

    #1305 : 区间求差 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 给定两个区间集合 A 和 B,其中集合 A 包含 N 个区间[ A1, A2 ], [ A3,  ...