haskell中的cps
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的更多相关文章
- haskell中的monad
monad本意是单子.在haskell中,第一个接触的基本都是IO action,通过把IO动作包装起来我们能很方便的与现实世界进行数据交换.但其实monad的用途不止如此,monad还能讲一系列操作 ...
- haskell中的do
在haskell中,有一个do的语句专门用来做一些不那么“干净”的事情,比如读写都需要用do来开头 一开始以为do的作用是做monad,后来发现是错误的,其实do做的事情是包裹一个顺序操作 比如在如下 ...
- haskell中的let和where
haskell中有两种定义局部变量的方法let和where,方法分别如下 roots a b c = ((-b + det) / (a2), (-b - det) / (a2)) *a*c) a2 = ...
- Haskell中cabal install glib遇到的问题
1. 运行命令cabal install glib时出现错误: Cannot find gtk2hsC2hs Please install `gtk2hs-buildtools` first and ...
- Haskell语言学习笔记(30)MonadCont, Cont, ContT
MonadCont 类型类 class Monad m => MonadCont m where callCC :: ((a -> m b) -> m a) -> m a in ...
- Haskell 笔记 ③
①循环?NO!请递归思考问题! 手艹一个求列表中最大值代码,C语言中习惯性for扫一下比较出最大值.但是可以用递归! maximum'::(Ord a)=>[a]->a maximum' ...
- haskell io模块
haskell中的io模块主要是用于读写文件屏幕的,通过import IO来导入 其中有如下常用定义 data IOMode = ReadMode | WriteMode | AppendMode | ...
- Haskell 与范畴论
说到 Haskell,这真是一门逼格极高的编程语言,一般初学者如果没有相关函数式编程的经验,入门直接接触那些稀奇古怪的概念,简直要跪下.现在回想起来,隐隐觉得初学者所拥有的命令式编程语言(impera ...
- JavaScript中的Partial Application和Currying
这篇文章是一篇学习笔记,记录我在JS学习中的一个知识点及我对它的理解,知识点和技巧本身并不是我原创的.(引用或参考到的文章来源在文末) 先不解释Partial Application(偏函数应用)和C ...
随机推荐
- android Drawable的问题
1.资源解析成Drawable getDrawable(int id); 挺简单一方法,可是 require api 21......如何向下兼容呢???? 幸亏有ContextCompat类...( ...
- Flask——route
Flask——route 关于路由flask中有三种方法(例子)处理: flask.Flask.route 装饰器(关于装饰器可以参考该文),这时最常见的使用方法,在装饰器的参数中加入想要的路由即可, ...
- PIC32MZ tutorial -- Output Compare
Output Compare is a powerful feature of embedded world. The PIC32 Output Compare module compares the ...
- PHP时间日期
PHP常用的几个时间日期函数有:date(),mktime(),strtotime(); 一.string date ( string $format [, int $timestamp ] ) 函数 ...
- mysql之导入与导出
SELECT INTO…OUTFILE语句把表数据导出到一个文本文件中,并用LOAD DATA …INFILE语句恢复数据.但是这种方法只能导出或导入数据的内容,不包括表的结构,如果表的结构文件损坏, ...
- [笔记]Modelsim系列01:编译Altera库的方法
意义:一劳永逸,不用每次对那些包含Quartus II生成文件的工程进行功能仿真时,都需要重新编译一堆东西.节约时间成本. 版本:ModelSim SE 6.5d 打开Modelsim软件,默认会打开 ...
- java 堆栈
堆栈(stack).位于通用RAM中,但通过它的“堆栈指针”可以从处理器哪里获得支持.堆栈指针若向下移动,则分配新的内存:若向上移动,则释放那些 内存.这是一种快速有效的分配存储方法,仅次于寄存器. ...
- 实践一:Linux基础实践
一.Linux基础实践 1.1 1. 掌握软件源的维护方法,配置系统使用软件源镜像.掌握通过软件源来查找,安装,卸载,更新软件的方法. 这部分内容在许多学长学姐的报告里都有很详细的讲解,我在此就不赘述 ...
- sql中 查询条件出现单引号和特殊字符处理
1.两个单引号转为一个单引号 example: select * from tb where name=' '' ' 2.如果出现 "_","%" 需要用 ...
- iOS设计模式-Block实现代理的逻辑
在A页面,点击跳转到B页面,B页面操作完,回到A页面,并刷新A页面的内容.典型的例子,就是在一个列表里,点击新增,跳到新增页面,新增完,把数据传回给列表页,并刷新列表页里的内容. 这个,我平时一般是通 ...