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. SQL Server 之AdventureWorks 2008 安

    学习背景:<SQL Server 2008 编程入门经典> SQL Sever 版本 SQL Server 2008 R2 方法一: 1:AdventureWorks 2008 下载地址: ...

  2. leetcode 136

    136. Single Number Given an array of integers, every element appears twice except for one. Find that ...

  3. 属性传值,协议传值,block传值,单例传值四种界面传值方式

    一.属性传值 对于属性传值而言,相对于其它的三种 方法来说,是最基础,最简单的一种 方法,但,属性传值 有很大的局限性,因为是适用于第一个界面向第二个界面传 值,第二个向第三个界面传值等等.N界面向N ...

  4. jquery源码学习之extend

    jquery的extend方法现项目中经常使用,现在了解一下它的实现. 说起extend就要先了解一个jQuery的$.extend和$.fn.extend作用及区别 jQuery为开发插件提拱了两个 ...

  5. DIRECTORY_SEPARATOR 和 PATH_SEPARATOR的区别

    DIRECTORY_SEPARATOR:目录分隔符,linux上就是’/’    windows上是’\’ PATH_SEPARATOR:路径分隔符,include多个路径使用,在win下,当你要in ...

  6. android系统中查看哪些端口被哪些应用打开

    1 查看哪些端口开放,netstat 2 根据端口号获取到UID,比如端口号为10050,转成16进制是2742,使用命令grep -i 2742 /proc/net/tcp6,就能看到其UID,假如 ...

  7. UBUNTU9.10下安装TFTP学习笔记一(arm学习SEED-138板子)

    擦,刚刚写的没保存都丢了,郁闷中~~~~ 简单重写 1什么是TFTP .安装TFTP(TFTP(Trivial File Transfer Protocol,简单文件传输协议)是TCP/IP协议族中的 ...

  8. 动态ViewPager导航页面

    今天新学知识总计,个人信息,仅供参考: item设置: viewpager页面设置: <?xml version="1.0" encoding="utf-8&quo ...

  9. Docx读写Word

    Docx.dll功能比较强大,具备以下功能: 创建新的word文档或者读取已有的world文档 替换书签处内容: 插入表格或者在已有表格新增数据行: 插入图片,轻松设置图片大小: 保存或者另存为: 分 ...

  10. 使用引脚模拟PWM波控制引脚

    /********************************* 代码功能:输出PWM波控制引脚 使用函数: 创作时间:2016*10*07 作者邮箱:jikexianfeng@outlook.c ...