Atitit 函数式编程与命令行语言的区别与优缺点 目录 1. 常见的函数式语言 2 1.1. 命令行 bat 2 1.2. Sql h5 css 正则表达式 2 1.3. 工作流语言 anno注
Atitit 函数式编程与命令行语言的区别与优缺点
目录
2.2. 1.1. 函数式程序就是一个表达式。命令式程序就是一个冯诺依曼机的指令序列。 3
2.3. 1.2. 面向对象语言中,数据类型分为两种——基本类型和对象类型(即class)。 函数式语言中,数据类型也分为两种——基本类型和函数类型。 3
2.4. 1.3. Oo,以数据为中心,op围绕数据。。Fp相反 3
2.5. 1.6. 一般来说,递归这种方式于循环相比被认为是更符合人的思维的..为什么递归下降法比循环好了和。。 3
2.6. 1.7. 函数式程序是可以保存状态的,只不过它们用的不是变量,而是函数。 3
2.8. 函数式编程思维, 就是用计算(函数)来表示程序, 用计算(函数)的组合来表达程序的组合的思维方式 4
2.9. 命令式编程是面向计算机硬件的抽象 函数式编程是面向数学的抽象 函数式程序就是一个表达式 4
2.11. 命令式语言主要组成(运算符与控制结构,if,foreach) 函数式语言(全部是函数) 5
2.13. 函数式编程,但我觉得lisp是面向AST编程,更接近计算机组成和元 5
2.16. 高阶逻辑 用了函数式,就不要在想循环,赋值这些低阶逻辑了,而应该更高阶的思考问题, 5
2.17. 组合子逻辑 或又叫 自底向上的设计 面向对象是自顶向下的设计,函数式是自底向上的设计 6
2.18. 函数式:计算产生结果(依赖函数嵌套运算); 命令式:计算影响结果(依赖赋值的副作用影响后续运算)。 6
3.3. 符合编程未来趋势。。未来趋势就是可读性越来约高,性能相对下降 7
3.4. 大局观 描述了做什么what,而不是怎么做how 7
3.5. 方便机器实现并行 命令行的并行目前只能由程序员自己实现,繁琐 7
3.10. 用计算来表示程序, 用计算的组合来表达程序的组合. 而非函数式编程则习惯于用命令来表示程序, 用命令的顺序执行来表达程序的组合. 9
3.11. 提升性能惰性求值(Lazy evaluation,也称作call-by-need) 9
3.12. 提升可靠性 没有变量 外部遍历等全部参数表传入 9
4.3. 函数式编程也有不太擅长的场合,比如处理可变状态和处理IO, 10
1. 常见的函数式语言
1.1. 命令行 bat
1.2. Sql h5 css 正则表达式
1.3. 工作流语言 anno注解
1.4. xml..json..yaml 模式的语言
2. 主要区别 函数就是一个ast
2.1. 函数式语言 离机器太远 更抽象 命令行近
2.2. 在面向对象编程中,我们把对象传来传去,那在函数式编程中,我们要做的是把函数传来传去,而这个,说成术语,我们把他叫做高阶函数
2.3. 1.1. 函数式程序就是一个表达式。命令式程序就是一个冯诺依曼机的指令序列。
2.4. 状态传递函数式编程中的则是将其保存在函数的参数中,作为函数的附属品来传递。
2.5. 。递归是在描述这个问题的定义。 而递归与循环在编程模型和思维模型上最大的区别则在于: 循环是在描述我们该如何地去解决问题
2.6. 1.2. 面向对象语言中,数据类型分为两种——基本类型和对象类型(即class)。
函数式语言中,数据类型也分为两种——基本类型和函数类型。
2.7. 1.3. Oo,以数据为中心,op围绕数据。。Fp相反
2.8. 1.6. 一般来说,递归这种方式于循环相比被认为是更符合人的思维的..为什么递归下降法比循环好了和。。
2.9. 1.7. 函数式程序是可以保存状态的,只不过它们用的不是变量,而是函数。
状态保存在函数的参数中,也就是说在栈上。如果你需要保存一个状态一段时间并且时不时的修改它,那么你可以编写一个递归函数
2.10. 1.8. pattern match
模式匹配的case of 语句。
2.11. 函数式编程思维, 就是用计算(函数)来表示程序, 用计算(函数)的组合来表达程序的组合的思维方式
2.12. 命令式编程是面向计算机硬件的抽象 函数式编程是面向数学的抽象 函数式程序就是一个表达式
命令式编程是面向计算机硬件的抽象,有变量(对应着存储单元),赋值语句(获取,存储指令),表达式(内存引用和算术运算)和控制语句(跳转指令),一句话,命令式程序就是一个冯诺依曼机的指令序列。 而函数式编程是面向数学的抽象,将计算描述为一种表达式求值,一句话,函数式程序就是一个表达式
2.13. 名词抽象世界 动词抽象世界
如果面向对象式编程是用名词抽象世界,从而达到对于事物的封装和重用。
那函数式编程对应的就是用动词抽象世界,从而达到对于行为的封装和重用。
2.14. 命令式语言主要组成(运算符与控制结构,if,foreach) 函数式语言(全部是函数)
2.15. 函数只是有唯一成员的类的实例而已
2.16. 函数式编程,但我觉得lisp是面向AST编程,更接近计算机组成和元
2.17. 表达式化
在最初的时候,需要转变观念,去可变量,去循环,把命令式改成表达式,注意,这只是把你丢在荒山野岭让你感受一下,离开熟悉的环境,地球依然在转,但是有个重点,那就是一切都是表达式; 为什么是表达式呢?这个问题就像为什么鱼在水里? 因为函数式建立在lambda演算之上而非图灵机,只不过两者被证明等价,所以你可以在你的机器上跑全是表达式的代码,就如有人证明天空适合鱼生存,所以鱼可以在天上游
2.18. 1.5 数据与行为分离
这也是和面向对象不一致的地方,面向对象强调数据与行为绑定,但函数式不是,确切的说函数式 函数与数据等价,所以你才可以将函数当参数与返回值,你在设计时,切勿让数据自己长腿能跑,其次,行为必须消除副作用,不可以偷偷把数据改了,习惯第一条后,应该不会的
2.19. 高阶逻辑 用了函数式,就不要在想循环,赋值这些低阶逻辑了,而应该更高阶的思考问题,
这比转化表达式更难,函数式又叫声明式,也就是你要做什么,只要说一下就行,而非写个遍历,做个状态判断,用函数式你不需要考虑这些,你不知道函数式的列表是怎么遍历的,中间向两边? 从后往前?这也是为何函数式适合并发的原因之一,你想知道列表中大于3的数有多少,只要,list.count(_ > 3) 而不是写循环,你可以直接写你的业务,不要拘泥于细节,有点像sql, 你需要什么告诉电脑就行,你或许会问,count foreach filter 这些函数怎么来的? 因为有了他们你才不需要写循环,他们把你留在高阶逻辑中,这个问题的答案请看下面
2.20. 组合子逻辑 或又叫 自底向上的设计 面向对象是自顶向下的设计,函数式是自底向上的设计
函数式和OO是反的,面向对象是自顶向下的设计,函数式是自底向上的设计,也就是先定义最基本的操作,然后不断组合,不断堆积以满足你的所有需要,如sql定义了select, from, where...这几个组合子,来满足你的查询需求,同理函数式语言会提供foreach, map等组合子(操作)来满足你的需求,所以你必须自下而上的设计你的代码结构,并且满足你的需求,当你只用组合子写代码时,你会发现你写的全是高阶逻辑
如果这些已有组合子满足不了你,你就得自己写,foreach不行,你就自己写递归,我告诉你,递归背后也是组合子,这里一些'大神'应该不知道,在图灵机里,递归就是方法不断调用自己没什么好说的,但是在lambda演算中,匿名函数是没法调用自己的,所以递归是用Y组合子(又叫不动点组合子)把递归函数自己求解出来再调用的,这才可以实现递归,并与图灵机的循环等价,有点跑题了,总之要想顺手的写函数式,最好用面向组合子的设计,注意,不是必须,组合子演算和lambda演算可以相互转化,也就是,你完全可以写一堆杂乱的表达式,但没有组合子逻辑来得清爽,Haskell大规模使用monad这个特殊组合子,始其变得统一整洁
2.21. 函数式:计算产生结果(依赖函数嵌套运算); 命令式:计算影响结果(依赖赋值的副作用影响后续运算)。
2.22. 申明式的(Declarative)
对于申明式的编程范式,你不在需要提供明确的指令操作,所有的细节指令将会更好的被程序库所封装,你要做的只是提出你要的要求,申明你的用意即可
3. 优点
3.1. Dsl 语义化 可读性高
3.2. 代码简洁
3.3. 符合编程未来趋势。。未来趋势就是可读性越来约高,性能相对下降
3.4. 大局观 描述了做什么what,而不是怎么做how
3.5. 方便机器实现并行 命令行的并行目前只能由程序员自己实现,繁琐
并发执行
不需要任何改动,所有FP程序都是可以并发执行的。由于根本不需要采用锁机制,因此完全不需要担心死锁或是并发竞争的发生。在FP程序中没有哪个线程可以修改任何数据,更不用说多线程之间了。这使得我们可以轻松的添加线程,至于那些祸害并发程序的老问题,想都不用想!
既然是这样,为什么没有人在那些高度并行的那些应用程序中采用FP编程呢?事实上,这样的例子并不少见。爱立信开发了一种FP语言,名叫Erlang,并应用在他们的电信交换机上,而这些交换机不仅容错度高而且拓展性强。许多人看到了Erlang的这些优势也纷纷开始使用这一语言。在这里提到的电信交换控制系统远远要比华尔街上使用的系统具有更好的扩展性也更可靠。事实上,用Erlang搭建的系统并不具备可扩展性和可靠性,而Java可以提供这些特性。Erlang只是像岩石一样结实不容易出错而已。
3.6. 方便机器分析 基于不变形 依赖少
如果是函数式程序,编译器就可以对代码进行分析,然后可能分析出生成字符串s1和s2的两个函数可能会比较耗时,进而安排它们并行运行。这在指令式编程中是无法做到的,因为每一个函数都有可能修改其外部状态,然后接下来的函数又可能依赖于这些状态的值。在函数式编程中,自动分析代码并找到适合并行执行的函数十分简单,和
3.7. 热部署 也是基于不变形
最理想的情况是更新相关的代码而不用暂停系统的其他部件。对指令性程序来说是不可能的。想想看,试着在系统运行时卸载掉一个Java的类然后再载入这个类的新的实现,这样做的话系统中所有该类的实例都会立刻不能运行,因为该类的相关状态已经丢失了。这种情况下可能需绞尽脑汁设计复杂的版本控制代码,需要将所有这种类正在运行的实例序列化,逐一销毁它们,然后创建新类的实例,将现有数据也序列化后装载到这些新的实例中,最后希望负责装载的程序可以正确的把这些数据移植到新实例中并正常的工作。这种事很麻烦,每次有新的改动都需要手工编写装载程序来完成更新,而且这些装载程序还要很小心,以免破坏了现有对象之间的联系。理论上是没问题,可是实际上完全行不通。
FP的程序中所有状态就是传给函数的参数,而参数都是储存在栈上的。这一特性让软件的热部署变得十分简单。只要比较一下正在运行的代码以及新的代码获得一个diff,然后用这个diff更新现有的代码,新代码的热部署就完成了。其它的事情有FP的语言工具自动完成!如果还有人认为这只存在于科幻小说中,他需要再想想:多年来Erlang工程师已经使用这种技术对它们的系统进行升级而完全不用暂停运行了。
3.8. 机器辅助优化及证明
FP语言有一个特性很有意思,那就是它们是可以用数学方法来分析的。FP语言本身就是形式系统的实现,只要是能在纸上写出来的数学运算就可以用这种语言表述出来。于是只要能够用数学方法证明两段代码是一致的,编译器就可以把某段代码解析成在数学上等同的但效率又更高的另外一段代码7。 关系数据库已经用这种方法进行优化很多年了。没有理由在常规的软件行业就不能应用这种技术。 另外,还可以用这种方法来证明代码的正确性,甚至可以设计出能够自动分析代码并为单元测试自动生成边缘测试用例的工具出来!对于那些对缺陷零容忍的系统来说,这一功能简直就是无价之宝。例如心脏起搏器,例如飞行管控系统,这几乎就是必须满足的需求。哪怕你正在开发的程序不是为了完成什么重要核心任务,这些工具也可以帮助你写出更健壮的程序,直接甩竞争对手n条大街。
3.9. 抽象化控制结构
惰性求值技术提供了更高阶的抽象能力,这提供了实现程序设计独特的方法。比如说下面的控制结构:
3.10. 用计算来表示程序, 用计算的组合来表达程序的组合. 而非函数式编程则习惯于用命令来表示程序, 用命令的顺序执行来表达程序的组合.
3.11. 提升性能惰性求值(Lazy evaluation,也称作call-by-need)
,是在将表达式赋值给变量(或称作绑定)时并不计算表达式的值,而在变量第一次被使用时才进行计算。这样就可以通过避免不必要的求值提升性能。在Scala里,通过lazy val来指定一个变量是惰性求值的,如下面的示例所示:
3.12. 提升可靠性 没有变量 外部遍历等全部参数表传入
,也没有函数可以修改在它的作用域之外修改什么值给其他函数继续使用(在指令式编程中可以用类成员或是全局变量做到)。这意味着决定函数执行结果的唯一因素就是它的返回值,而影响其返回值的唯一因素就是它的参数。
这正是单元测试工程师梦寐以求的啊。现在测试程序中的函数时只需要关注它的参数就可以了。完全不需要担心函数调用的顺序,也不用费心设置外部某些状态值。唯一需要做的就是传递一些可以代表边界条件的参数给这些函数。相对于指令式编程,如果FP程序中的每一个函数都能通过单元测试,那么我们对这个软件的质量必将信心百倍。反观Java或者C++,仅仅检查函数的返回值是不够的:代码可能修改外部状态值,因此我们还需要验证这些外部的状态值的正确性。在FP语言中呢,就完全不需要。
的,因为FP程序中的错误不依赖于之前运行过的不相关的代码。而在一个指令式程序中,一个bug可能有时能重现而有些时候又不能。因为这些函数的运行依赖于某些外部状态, 而这些外部状态又需要由某些与这个bug完全不相关的代码通过某个特别的执行流程才能修改。在FP中这种情况完全不存在:如果一个函数的返回值出错了,它一直都会出错,无论你之前运行了什么代码。
一旦问题可以重现,解决它就变得非常简单,几乎就是一段愉悦的旅程。中断程序的运行,检查一下栈,就可以看到每一个函数调用时使用的每一个参数,这一点和指令式代码一样。不同的是指令式程序中这些数据还不足够,因为函数的运行还可能依赖于成员变量,全局变量,还有其他类的状态(而这些状态又依赖于类似的变量)。FP中的函数只依赖于传给它的参数,而这些参数就在眼前!还
。对于一个FP程序,你要做的仅仅是看一下函数的返回值。
把栈上的数据过一遍就可以得知有哪些参数传给了什么函数,这些函数又返回了什么值。当一个返回值看起来不对头的那一刻,跳进这个函数看看里面发生了什么。一直重复跟进下去就可以找到bug的源头!
4. 缺点
4.1. 少部分场景下,可读性可能不如常见命令式
4.2. 性能相比之下较差
4.3. 函数式编程也有不太擅长的场合,比如处理可变状态和处理IO,
要么引入可变变量,要么通过Monad来进行封装(如State Monad和IO Monad)
5. 其他特点
5.1. 函数式编程严重依赖递归
同样由于变量不可变,纯函数编程语言无法实现循环,这是因为For循环使用可变的状态作为计数器,而While循环或DoWhile循环需要可变的状态作为跳出循环的条件。因此在函数式语言里就只能使用递归来解决迭代问题,这使得函数式编程严重依赖递归。 通常来说,算法都有递推(iterative)和递归(recursive)两种定义,以阶乘为例,阶乘的递推定义为:
函数式编程严重依赖递归
般来说,递归这种方式于循环相比被认为是更符合人的思维的,即告诉机器做什么,而不是告诉机器怎么做。递归还是有很强大的表现力的,比如换零钱问题。
5.2. 没有控制指令了
控制结构将消失,select ,foreach 等...表格将取代select结构
5.3. lambda就是一个函数,
在数学符号中用这个希腊字母只是因为它更容易写。所以以后在谈及函数式编程的时候只要你听到lambda,把它在脑中翻译为“函数”就可以了
6. 综合使用,函数式为主 命令式为辅即可
如, 用一个简单的例子: 程序1: int a, b, r; void add_abs() { scanf("%d %d", &a, &b); r = abs(a) + abs(b) printf("%d", r); } 程序2: int add_abs(int a, int b) { int r = 0; r += abs(a); r += abs(b); return r; } 程序3: int add_abs(int a, int b) { return abs(a) + abs(b); } 程序1 是用命令来表示程序, 用命令的顺序执行来表示程序的组合, 不算函数式 程序2 是用函数来表示程序, 但在内部是用命令的顺序执行来实现, 不太函数式 程序3 是用函数来表示程序, 用函数的组合来表达程序的组合, 是完全的函数式编程
函数式编程在使用的时候的特点就是,你已经再也不知道数据是从哪里来了,每一个函数都是为了用小函数组织成更大的函数,函数的参数也是函数,函数返回的也是函数,最后得到一个超级牛逼的函数,就等着别人用他来写一个main函数把数据灌进去了。
至于说要如何体会这样的东西,题主把Haskell学会了就明白了。
7. 适用场合
们看完了函数式编程的特点,我们想想函数式编程的应用场合。
1. 数学推理
2. 并行程序
那么我们总体地说,其实函数式编程最适合地还是解决局部性的数学小问题,要让函数式编程来做CRUD,来做我们传统的逻辑性很强的Web编程,就有些免为其难了。
就像如果要用Scala完全取代今天的Java的工作,我想恐怕效果会很糟糕。而让Scala来负责底层服务的编写,恐怕再合适不过了。
而在一种语言中融入多种语言范式,最典型的C#。在C# 3.0中引入Lambda表达式,在C# 4.0中引入声明式编程,我们某些人在嘲笑C#越来越臃肿的同时,却忽略了,这样的语法糖,带给我们的不仅仅是代码书写上的遍历,更重要的是编程思维的一种进步。
好吧,那就让我们忘记那些C#中Lambda背后的实现机制,在C#中,还是在那些更纯粹地支持函数式编程的语言中,尽情地去体验函数式编程带给我们的快乐把!
8. Ref
(2 封私信 _ 23 条消息)什么是函数式编程思维? - 知乎.html
Atitit 函数式编程与命令式编程的区别attilax总结 qbf - attilaxAti - 博客园.html
函数式编程含义 - CSDN博客.html