函数式编程 - FP

Functional Programming For The Rest of Us
英文版 - 链接
翻译版 - 链接 链接2

指令式编程->函数式编程

本不应如此复杂,应该是可以被工程所用,但现实情况却不是
其历史断层的原因?
形式系统、阿隆佐、λ演算、二战、Lisp语言(PS:Common Lisp 程序员冰河,又叫伞哥->(http://tianchunbinghe.blog.163.com/blog/static/70012017113085719648/))

函数式编程,变量即符号只能赋一次值,采用函数来保存状态,状态保存在函数的参数中,也就是说在栈上。如果你需要保存一个状态一段时间并且时不时的修改它,那么你可以编写一个递归函数。在这里插入图片描述

FP客观上的优点:
1 单元测试, FP每个符号都是const,所以每个函数都没有副作用,谁也不能修改任何东西,没有函数可以在其作用域外修改什么值给其他函数继续使用(在指令式编程中可以做到),所以函数的执行结果即返回值,其影响返回值的唯一因素即参数。
单元测试师在测试时候不用担心函数的调用顺序,不用设置某些外部状态值,只用关注其函数参数即可。其测试过程基本上就是传递一些待变边界条件的参数给函数
2 调试差错, FP程序的错误不依赖之前运行的代码或外部状态,所以任何错误都可以重现并会一直出错,查看各返回值,找到错误,解决它。
3 并发执行,所有FP程序都可以并发,不用考虑锁机制和竞争等,像岩石一样不易出错。即使是单线程的FP程序,在某些编译器上可以将其优化为多CPU上运行的并发程序。(Erlang语言)
4 热部署, FP的程序中所有状态就是传给函数的参数,而参数都是储存在栈上的。这一特性让软件的热部署变得十分简单。只要比较一下正在运行的代码以及新的代码获得一个diff,然后用这个diff更新现有的代码,新代码的热部署就完成了。其它的事情有FP的语言工具自动完成!如果还有人认为这只存在于科幻小说中,他需要再想想:多年来Erlang工程师已经使用这种技术对它们的系统进行升级而完全不用暂停运行了。
5 机器辅助优化及证明, FP语言是可以用数学方法来分析的,本身就是形式系统的实现,因此只要是能在纸上写出来的数学运算就可以用这种语言表述出来。即只要能够用数学方法证明两段代码是一致的,编译器就可以把某段代码解析成在数学上等同的但效率又更高的另外一段代码!还可以用这种方法来证明代码的正确性,甚至可以设计出能够自动分析代码并为单元测试自动生成边缘测试用例的工具出来!

FP语言提供了一种特别的抽象工具,这种工具将帮助使用者编写FP代码,让他们甚至都没想到要修改变量的值。高阶函数就是这种工具之一。对其他函数进行操作(比如说把这些函数当成参数)的函数,就是所谓的高阶函数。变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数

FP语言编程完全可以以概念编程,基本不需要设计模式。适配器模式,Currying用于减少函数参数的数量,一种可以快速且简单的实现函数封装的捷径。我们可以更专注于自己的设计,编译器则会为你编写正确的代码!在这里插入图片描述
Continuation (Continuation Pass Style, CPS) ,我们对函数的理解只有一半是正确的,因为这样的理解基于一个错误的假设:函数一定要把其返回值返回给调用者。按照这样的理解,continuation就是更加广义的函数。这里的函数不一定要把返回值传回给调用者,相反,它可以把返回值传给程序中的任意代码。continuation就是一种特别的参数,把这种参数传到函数中,函数就能够根据continuation将返回值传递到程序中的某段代码中。这时square就是add的一个continuation。上面两段程序中j的值都是225。在这里插入图片描述在这里插入图片描述

  • 用CPS风格写出来的程序不需要栈,但是每次调用函数的时候都会要多加一个参数。非CPS风格的程序不需要额外的参数但又需要栈才能运行。栈里面存的是什么?仅仅是参数还有一个供函数运行结束后返回的程序指针而已。这个时候你是不是已经恍然大悟了?对啊,栈里面的数据实际上就是continuation的信息!栈上的程序返回指针实质上就是CPS程序中需要调用的下一个函数!想要知道add(5, 10)的continuation是什么?只要看它运行时栈的内容就可以了。
  • continuation是显式的传递该地址并且因此代码就不局限于只能返回到函数被调用的地方了。前面说过,continuation就是函数,而在我们特制的语言中函数就是类的实例,那么可以得知栈上指向函数返回地址的指针和continuation的参数是一样的,因为我们所谓的函数(就像类的一个实例)其实就是指针。这也意味着在程序运行的任何时候,你都可以得到当前的continuation(就是栈上的信息)。
  • 只要得到了当前的continuation并将它保存起来,就相当于保存了程序的当前状态:在时间轴上把它冻结起来了。这有点像操作系统进入休眠状态。continuation对象保存了足够的信息随时可以从指定的某个状态继续运行程序。在切换线程的时候操作系统也是这样做的。唯一的区别在于它保留了所有的控制权利。当请求某个continuation对象时(在Scheme语言中是通过调用call-with-current-continuation函数实现的)得到的是一个存有当前continuation的对象,也就是栈对象(在CPS中也就是下一个要执行的函数)。可以把这个对象保存做一个变量中(或者是存在磁盘上)。当以该continuation对象“重启”该程序时,程序的状态就会立即“转换”为该对象中保存的状态。这一点和切换回一个被暂停的线程或是从系统休眠中唤醒很相像,唯一不同的是continuatoin对象可以反复的这样使用。当系统唤醒后,休眠前保存的信息就会销毁,否则你也可以反复的从该点唤醒系统,就像乘时光机回到过去一样。有了continuation你就可以做到这一点!

惰性求值(反义词,严格求值) ,对于FP这种并发式的程序,函数的执行顺序不一定是固定的,但需要按某种顺序执行时,惰性求值可以在需要某一个函数的值的时候才去执行这个函数。优点:
代码优化:惰性求值使得代码具备了巨大的优化潜能。支持惰性求值的编译器会像数学家看待代数表达式那样看待函数式程序:抵消相同项从而避免执行无谓的代码,安排代码执行顺序从而实现更高的执行效率甚至是减少错误。在此基础上优化是不会破坏代码正常运行的。严格使用形式系统的基本元素进行编程带来的最大的好处,是可以用数学方法分析处理代码,因为这样的程序是完全符合数学法则的。
抽象化控制结构: 在这里插入图片描述
无穷数据结构: 在这里插入图片描述
缺点:
惰性求值当然也有其缺点。其中最大的一个就是惰性,现实世界中很多问题还是需要严格求值的,在惰性语言中没人能保证第一行会先第二行之前执行!这也就意味着我们不能处理IO,不能调用系统函数做任何有用的事情(这些函数需要按照顺序执行,因为它们依赖于外部状态),也就是说不能和外界交互了!
如果在代码中引入支持顺序执行的代码原语,那么我们就失去了用数学方式分析处理代码的优势(而这也意味着失去了函数式编程的所有优势)。幸运的是我们还不算一无所有。数学家们研究了不同的方法用以保证代码按一定的顺序执行(in a functional setting?)。这一来我们就可以同时利用到函数式和指令式编程的优点了!这些方法有continuations,monads以及uniqueness typing。这篇文章仅仅介绍了continuations,以后再讨论monads和uniqueness typing。有意思的是呢,coutinuations处理强制代码以特定顺序执行之外还有其他很多出处,这些我们在后面也会提及。

模式匹配:
在这里插入图片描述

  • 只是有人注意到很多函数中有非常复杂的switch结构(对于函数式程序而言更是如此),于是想到如果能把这层结构也抽象化就更好了。然后就把这个复杂的函数拆分成若干新的函数,并在这些函数的某些参数中应用模式(这和重载有点类似)。这样依赖当这个函数被调用的时候,编译器会在运行时将调用者传入的参数与各个新函数的参数定义进行比较,找出合适的那个函数来执行。合适的函数往往是参数定义上最具体最接近传入参数的那个函数。在这个例子中,当n为1时,可以用函数int fib(int n),不过真正调用的是int fib(1)因为这个函数更具体更接近调用者的要求。
    模式匹配一般来说要比这里举的例子更加复杂。比如说,高级模式匹配系统可以支持下面的操作:在这里插入图片描述
  • 在需要处理一大堆程序分支的时候!每当需要实现复杂的嵌套if语句的时候,模式匹配可以帮助你用更少的代码更好的完成任务。模式匹配的另外一个好处是每当需要添加或者修改程序分支时,再也不用面对那个庞大臃肿的函数了。只要添加(或者修改)相关的函数定义即可。有了模式匹配就不再需要四人帮的很多设计模式了。程序分支越多越复杂,模式匹配就越有用。而在习惯使用这一技术之后,你可能会怀疑没有它你一天都过不下去了。

Closure,与在lambda演算限制下将函数作为参数传递不同,在指令式语言中要做到同样的事情需要支持一个有趣的特性,人们常把它称为lexical closure,closure将函数编程与面向对象的方法结合了起来。一个Closure就是一个“捕获”或“携带”了其被生成的环境中、所属的变量范围内所引用的所有变量的函数。下一次为了保存并传递某些状态而创建类的时候,想想closure。它能在运行时从相应的域中获得变量,从而可以把该变量当初“成员变量”来访问,也因为这样,就不再需要去创建一个成员变量了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值