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. Linux 命令 ls -l

    一.ll命令 ll并不是linux下一个基本的命令,它实际上是ls -l的一个别名. Ubuntu默认不支持命令ll,必须用 ls -l,这样使用起来不是很方便. 如果要使用此命令,可以作如下修改:打 ...

  2. teamviewer现在无法捕捉屏幕,这可能是由于快速的用户切换或远程桌面会话已经断开

    解决方法:      不用远程连接过去开启teamview,直接在在电脑本机上手动开启teamview就可以了 即:如果是mstsc远程过去开启,则会有这个错误提示,需要让服务器连接显示器,手动去登录 ...

  3. 码农谷 找出N之内的所有完数

    题目描述 一个数如果恰好等于它的因子之和,这个数就称为"完数". 例如,6的因子为1.2.3,而6=1+2+3,因此6是"完数". 编程序找出N之内的所有完数, ...

  4. 第五百八十一天 how can I 坚持

    也是醉了,现在买个手机都特么搞饥饿营销,吹牛B就要付出吹牛B的代价,哎,好伤感. 晚上学习也没学好.感觉人和人之间的信任怎么都没了呢..但愿是我想多了,其实就是我想多了,以后说话还是要多注意. 睡觉吧 ...

  5. JavaScript 基础(七) 箭头函数 generator Date JSON

    ES6 标准新增了一种新的函数: Arrow Function(箭头函数). x => x *x 上面的箭头相当于: function (x){ return x*x; } 箭头函数相当于匿名函 ...

  6. keyup keydown keypress 区别

    测试的浏览器环境: chrome 版本 43.0.2357.134 mfirefox 版本 24.0IE6(绿色版 IE.exe)IE7IE8IE9 搜狗拼音输入法3.5(3.5.0.1089)网吧专 ...

  7. ArrayEasyFinish

    (1)Plus One 解题思路:模拟现实中做加法的方式,在个位加一,并考虑进位的情况.代码如下: public class Solution { public int[] plusOne(int[] ...

  8. XML团队介绍发布!

    三个臭皮匠,赛过诸葛亮 /********************************************************************/ 成员: 姓名:项浩哲 职务:项目经 ...

  9. Windows 窗体设计器中的设计时错误

    当修改窗体里面某一项时导致窗体报错,但是编译运行没问题,报"依赖项问题"则只需要把报错的那个依赖项删除后再重新引用.

  10. clone()与clone(true)的区别

    clone()方法用于复制一个元素,但是被复制出来的元素不具备复制的功能,如果希望被复制出来的元素也具备复制的功能,需要给clone()方法加上true这个参数,即clone(true). 例如: & ...