函数式编程与面向对象编程的比较
什么是函数式编程?
是一种编程范型,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。
可以接受函数当作输入(引数)和输出(传出值)。
什么是面向对象编程?
是种具有对象概念的程序编程范型,同时也是一种程序开发的方法。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象。
函数式编程的基本单位是函数,
面向对象编程的基本单位是对象.
面向对象的思路,更加符合人类日常生活的概念.
面向对象和面向过程的区别
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
编程范式
编程语言主要有三种类型[3]:
- 命令式编程(Imperative Programming): 专注于”如何去做”,这样不管”做什么”,都会按照你的命令去做。解决某一问题的具体算法实现。
- 函数式编程(Functional Programming):把运算过程尽量写成一系列嵌套的函数调用。
- 逻辑式编程(Logical Programming):它设定答案须符合的规则来解决问题,而非设定步骤来解决问题。过程是事实+规则=结果。
命令式编程 关注的是 步骤
有变量, 有赋值, 有控制(if,else), 有状态
函数式编程 关心数据之间的映射关系,
全都是计算,都是表达式求值.
一个函数的值仅决定于函数参数的值,不依赖其他状态。
不依赖外部状态
函数与方法
方法就是命令式编程中的函数,而函数则是函数式编程中的函数。
函数式语言中的条件语句,循环语句等也不是命令式编程语言中的控制语句,而是一种表达式。
- “表达式”(expression)是一个单纯的运算过程,总是有返回值;
- “语句”(statement)是执行某种操作(更多的是逻辑语句。),没有返回值。
严格意义上的函数式编程意味着不使用可变的变量,赋值,循环和其他命令式控制结构进行编程。
状态
命令式编程 :有了状态,我们的程序才能不断往前推进,一步步向目标靠拢的。(根据状态进行控制)
函数式编程强调无状态,不是不保存状态,而是强调将状态锁定在函数的内部,不依赖于外部的任何状态。更准确一点,它是通过函数创建新的参数或返回值来保存程序的状态的。
状态完全存在栈上。
函数即不依赖外部的状态也不修改外部的状态,函数调用的结果不依赖调用的时间和位置,这样写的代码容易进行推理,不容易出错。
到这里,我先试着用我所学过的js 来去理解一下
1.有语句, 和表达式
语句是用来控制, 有逻辑语句, 没有返回值
表达式是 一种运算过程.
let num 是一个声明式
num = 9 是一个 表达式(赋值运算) 是有返回值的 返回9
num > 9 也是个表达式(比较运算) 是有返回值的 返回 true
而if () {} 这个就是逻辑语句, 用来进行对状态(也就是num)进行判断,进行控制.
console.log() 这个应该是个语句吧, 因为怎么看他都只是一个动作, 而不是计算一个返回值.
let num = 9;
if (num > 9) {
console.log("bigger than 9");
}
函数式编程的意思就是, 每个句子,都应该是表达式, 都应该是有返回值的.
num > 9 ? "bigger than 9" : null
三目运算符, 虽然用的概念是if else, 但这是一个表达式, 是有返回值的.
函数与方法
function num (num) {/ 应该说这个函数是一个方法
console.log(++num);
}
function num (num) {/ 这个函数就是个函数, 因为他是强调有计算,有返回值?
return ++num;
}
不依赖外部变量
依赖外部变量的
let obj = {num : 1};
function num (obj) {
obj.num++;
}
num(obj); / 外部变量 obj 就变了. 并且这是没有返回值的.
不依赖外部变量.
关键是不会改变外部变量.
let obj = {num : 1};
function num (obj) {
let newObj = {// 如果num是个引用值, 我们应该用深度克隆
num : ++obj.num
};
return newObj;
}
let newObj = num(obj);
其实, 稍微想一下, 我们就会有一个疑问.
如果按照函数式编程,
那么就不应该出现 两个变量指向同一个引用地址吧?
let obj = {name : "mike"};
let obj1 = obj;
这个赋值是否违背了函数式编程?
因为这样就出问题了
let obj = {num : 1};
function returnNewObj (obj) {
let newObj = obj;
return obj
}
let newObj = returnNewObj(obj);
newObj.num = newObj.num + 1;
/这样的话 newObj 是会影响到 obj的
或者严格来讲, 也许函数编程应该是让所有语句都被包裹在函数当中?
把上面的翻译一下
function obj () {
return {num : 1};
}
function returnNewObj (obj) {
return obj();
}
function newObj (returnNewObj,obj) {
return returnNewObj(obj)
}
function addOne (newObj,returnNewObj,obj) {
return newObj(returnNewObj,obj).num + 1;
}
console.log(addOne(newObj,returnNewObj,obj));/2
哇塞,看段代码有点发憷啊.
难道说, 用函数式编程, 就根本不需要声明一个变量来接收什么值了?
唯一声明的变量类型就是 函数?
有点诡异啊.
如果真是这样, 那也能稍微理解了下面这句话,
每个函数的参数确定, 则输出就是确定的.
每个函数的执行都会返回一个值.
我怎么进行复用?比如 我就想复用 addOne(newObj,returnNewObj,obj)返回的值,
进行复用要怎么做?每次都写这么一段? 也不是不可以..
不过这显然不合理,因为如果没有变量赋值, 那我想要在前面的计算结果上进行计算,
就必然需要把这条链上需要的所有的函数都当做参数传进去吧?
如果函数式编程当中允许 let a = b 存在,
那也就是说, 不需要所有的表达式都放进函数当中.
这个应该是合理的.
不依赖外部参数的另一个特征?
按照我的理解, 当然也不是确定,
像下面这段代码中,
函数add 内部是直接访问了父级作用域链上的变量num;
而这种行为,在函数式编程当中应该是不允许的.
(这种行为是否称为域逻辑,回头百度一下)
let num = 1;
function add () {
let own = num;
return own;
}
函数式编程要求, 参数应该只能通过参数入口传进来.
function add (num) {
let own = num;
return own;
}
其实我个人也觉得尽量应该这么做,
因为这样我们就能看到数据的流向是比较明确的.
依赖关系是比较清晰的.
随意引用父级作用域链上的变量, 出错误时, 可能不明白这个数据是从何而来.
其实这也是一种启示.
我们都知道, 在子级函数当中,能够访问父级作用域链上的变量.
也就是说, 父级作用域链上的变量,
对子级函数而言, 读写操作权限是全部开放的.
let num = 1;
(function () {
num++;
(function () {
num++;
})()
})();
(function () {
num++;
})();
上面的三个函数均可以对num变量进行读写操作.
所以严格来讲, num 这种暴露方式 其实是很危险的?
3什么是函数式编程思维?
函数式编程关心数据的映射,命令式编程关心解决问题的步骤
首先判断节点是否为空;然后翻转左树;然后翻转右树;最后左右互换。
这就是命令式编程——你要做什么事情,你得把达到目的的步骤详细的描述出来,然后交给机器去运行。这也正是命令式编程的理论模型——图灵机的特点。一条写满数据的纸带,一条根据纸带内容运动的机器,机器每动一步都需要纸带上写着如何达到。
...这段代码体现的思维,就是旧树到新树的映射——对一颗二叉树而言,它的镜像树就是左右节点递归镜像的树。
... 通过描述一个 旧树->新树 的映射,而不是描述“从旧树得到新树应该怎样做”来达到目的。
...函数式的代码是“对映射的描述”,它不仅可以描述二叉树这样的数据结构之间的对应关系,任何能在计算机中体现的东西之间的对应关系都可以描述——比如函数和函数之间的映射(比如 functor);比如外部操作到 GUI 之间的映射(就是现在前端热炒的所谓 FRP)。它的抽象程度可以很高,这就意味着函数式的代码可以更方便的复用。同时,将代码写成这种样子可以方便用数学的方法进行研究(这就是为什么可以扯上“范畴上的”这种数学上的高深概念)
至于什么科里化、什么数据不可变,都只是外延体现而已。
这篇文章, 有点大神的味道,你闻到没?
当然他用的是python我不懂,
不过这个文章, 给我的印象是,
命令式编程 ------ 关注的是步骤, 也就是首先干什么,然后干什么
如果状态是什么状态时, 就干什么,
如果是另一个状态,另一种情况时 , 我就干另一件什么.
其实我在敲代码之前,就是这么考虑问题的.
因为敲代码,写项目, 可以看成是个工程项目,
工程项目,自然是要规划步骤,
要么是根据需求逆向反推,
要么是根据初始条件逼近需求.
总会在脑子想, 先干什么, 然后干什么.
函数式编程 ----- 关注的是 关系, 是对关系的描述.
所以很明显这跟数学更加相关?
因为正确清晰的描述一个关系,
在数学上更容易做到,
然后把数学上的关系, 转换成代码.
编程语言的主要类型,声明式编程,命令式编程()和函数式编程的区别
声明式编程:专注于”做什么”而不是”如何去做”。在更高层面写代码,更关心的是目标,而不是底层算法实现的过程。
ex: css, 正则表达式,sql 语句,html, xml…
命令式编程(过程式编程) : 专注于”如何去做”,这样不管”做什么”,都会按照你的命令去做。解决某一问题的具体算法实现。
函数式编程:把运算过程尽量写成一系列嵌套的函数调用。
函数式编程强调没有”副作用”,意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
所谓”副作用”(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。(详细了解函数式编程请看阮一峰的函数编程初探文章)
声明式编程和命令式编程的比较
命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。
声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。
如果我们花时间去学习(或发现)声明式的可以归纳抽离的部分,它们能为我们的编程带来巨大的便捷。首先,我可以少写代码,这就是通往成功的捷径。而且它们能让我们站在更高的层面是思考,站在云端思考我们想要的是什么,而不是站在泥里思考事情该如何去做。
这篇文章似乎和函数式编程没什么关系,讲的主要是 声明式编程.
按照这篇文章,我个人的理解是这样的.
let arr = [1,2,3];
for(let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
用步骤来描述的话, 应该是,
取出arr[0], 输出arr[0]
取出arr[1], 输出arr[1]
取出arr[2], 输出arr[2]
用概念抽象来说的话,应该是
我们说 遍历输出arr的所有项.
存在两个概念, 一个是 遍历, 一个是 输出
所以从某种角度上来讲, for语句本身 就是一种循环概念的体现.
how 的理解是, 一系列的步骤,
怎么理解 what呢?
实际上 what 首先是一种概念的实现. 其次才是一个功能的实现.
如果没有循环的概念, 就不会出现for
如果没有遍历的概念, 就不会出现 each?
Array.prototype.each = function (fn) {
for(let i = 0; i < this.length; i++) {
fn(this[i],i,this);
}
}