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 ...
随机推荐
- nodejs & nodemailer
nodejs & nodemailer https://www.npmjs.com/package/nodemailer 上面的連接裏面 有有一個例子: 可以直接拿來用: 安裝依賴,在pack ...
- <a>标签的href和onclick属性
讨论 <a>标签中,href和onclick事件的顺序与冲突问题. 首先明确一点:链接的onclick 事件被先执行,其次是href属性下的动作(页面跳转,或 javascript 伪链接 ...
- WebStorage 和 Cookie的区别
sessionStorage 和 localStorage 是HTML5 Web Storage API 提供的,可以方便的在web请求之间保存数据.有了本地数据,就可以避免数据在浏览器和服务器间不必 ...
- CentOS 问题集锦
在CentOS 6更新后,不可避免的会在启动选项中产生多个内核选项,一个内核文件大概占100兆左右(一般100M以下),可以使用以下命令进行删除多余的内核. 1.首先列出系统中正在使用的内核: # u ...
- android自定义进度圆与定时任务
先看代码:自定进度圆 public class ProgressCircle extends View { private Paint paint; private int strokewidth = ...
- webForm练习1(地区导航)
使用LINQ TO SQL类连接数据库. create database mydb go use mydb go CREATE TABLE [dbo].[ChinaStates] ( ) COLLAT ...
- VC++ excel 2 operations
LPDISPATCH lpDisp; //lpdispatch,接口指针 // 设置为FALSE时,加上app.Quit(); // 否则EXCEL.EXE进程会一直存在,并且每操作一次就会多开一 ...
- vc++ 如何添加右键弹出菜单
一.创建新工程 二.编辑菜单资源 1.添加菜单 按"Ctrl+R",双击"Menu"图标 2.于菜单编辑器内编辑菜单 四.添加代码(红色部分) void CCM ...
- Tableview 优化Cell的复用机制01
#import "ViewController.h" @interface ViewController ()<UITableViewDataSource> @end ...
- Facebook内部分享:26个高效工作的小技巧
春节假期马上就要结束了,该收收心进入新一年的工作节奏了~分享 26 个高效工作的小技巧,希望对大家有所帮助~(我发现自己只有最后一条执行得很好,并且堪称完美!) 1.时间常有,时间优先. 2.时间总会 ...