内容简介:函数式编程(英语:functional programming)或称函数程序设计,又称泛函编程,是一种编程典范,它将计算机运算视为数学上的函数计算,并且避免使用程序状态以及初中数学中定义设在一个变化过程中有两个变量x与y,如果对于x的每一个值,y都有唯一的值与它对应,那么就说x是自变量,y是x的函数.
函数式编程(英语:functional programming)或称函数程序设计,又称泛函编程,是一种编程典范,它将计算机运算视为数学上的函数计算,并且避免使用程序状态以及 易变对象 。
什么是数学函数
初中数学中定义
设在一个变化过程中有两个变量x与y,如果对于x的每一个值,y都有唯一的值与它对应,那么就说x是自变量,y是x的函数.
第一个图片每一个x 对应唯一一个y 值,此为函数
第二张图片x=5对应 多个y的值,所以此不为函数
举例来说,现在有这样一个数学表达式: (1 + 2) \* 3 - 4
传统的过程式编程,可能这样写:
var a = 1 + 2; var b = a \* 3; var c = b - 4; 复制代码
函数式编程要求使用函数,我们可以把运算过程定义为不同的函数,然后写成下面这样:
var result = subtract(multiply(add(1,2), 3), 4);
函数风格的编程特点
-
纯函数,没有副作用
-
引用透明
-
不修改状态
-
声明式与命令式
-
函数是第一等公民
纯函数,没有副作用
在Javascript中对于数组的操作,有些是纯的,有些就不是纯的。
var arr = [1,2,3,4,5]; arr.slice(0,3); //=\> [1,2,3] arr.slice(0,3); //=\> [1,2,3] 复制代码
Array.slice是纯函数,因为它没有副作用,对于固定的输入,输出总是固定的
var arr = [1,2,3,4,5]; arr.splice(0,3); //=\> [1,2,3] arr.splice(0,3); //=\> [4,5] arr.splice(0,3); //=\> [] 复制代码
Array.splice是不纯的,它有副作用,对于固定的输入,输出不是固定的
函数式编程强调没有“副作用”,意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值,即一个函数调用 n 次后依然返回同样的结果。
var a = 1; // 含有副作用,它修改了外部变量 a, 多次调用结果不一样 function test1() { a++ return a; } // 无副作用,没有修改外部状态, 多次调用结果一样 function test2(a) { return a + 1; } 复制代码
引用透明
指一个函数只会用到传递给它的变量以及自己内部创建的变量,不会使用到其他变量。
var a = 1; var b = 2; // 函数内部使用的变量并不属于它的作用域 function test1() { return a + b; } // 函数内部使用的变量是显式传递进去的 function test2(a, b) { return a + b; } 复制代码
不修改状态
在其他类型的语言中,变量往往用来保存"状态"。而函数式编程只是返回新的值,不修改系统变量,即是无破坏性的数据转换。
//修改状态 function addElement(y) { var x = [1, 2]; x.push(y) console.log(x) return x; } addElement(3) // x-\>[1,2,3] //不修改状态 function addElement(y) { var x = [1, 2]; var z = x.slice(0); z.push(y) console.log(x) return z; } addElement(3) 复制代码
声明式与命令式
•命令式:程序花费大量代码来描述用来达成期望结果的特定步骤,即"How to do"
•声明式:程序抽象了控制流过程,花费大量代码描述的是数据流,即"What to do"
以做蔬菜沙拉举例
-
声明式: 蔬菜.做成菜(蔬菜沙拉)
-
命令式: 洗干净(蔬菜) 混合(蔬菜, 沙拉) 放入盘中(混合物)
举一些栗子:chestnut::
希望得到一个数组每个数据平方后的和
// 命令式 function mysteryFn (nums) { let squares = [] let sum = 0 // 1\. 创建中间变量 for (let i = 0; i \< nums.length; i++) { squares.push(nums[i] \* nums[i]) // 2\. 循环计算平方 } for (let i = 0; i \< squares.length; i++) { sum += squares[i] // 3\. 循环累加 } return sum } // 以上代码都是 how 而不是 what… 复制代码
// 声明式 const mysteryFn = (nums) =\> sumFn(squaresFn(nums)) function squaresFn(ary) { let squares = [] for (let i = 0; i \< ary.length; i++) { squares.push(ary[i] \* ary[i]) } return squares } function sumFn(ary) { let sum = 0 for (let i = 0; i \< ary.length; i++) { sum += ary[i] } return sum } 复制代码
函数是第一等公民
所谓”第一等公民”(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
下面这些术语都是围绕这一特性的应用:
闭包
函数内定义了局部变量并且返回可缓存的函数. 变量在返回的函数内也是可被访问的, 此处创建了一个闭包
// test1 是普通函数 function test1() { var a = 1; // test2 是内部函数 // 它引用了 test1 作用域中的变量 a // 因此它是一个闭包 return function test2() { return a + 1; } } 复制代码
函数组合 (Composition)
前面提到过,函数式编程的一个特点是通过串联函数来求值。然而,随着串联函数数量的增多,代码的可读性就会不断下降。函数组合就是用来解决这个问题的方法。
var compose = function(f,g) { return function(x) { return f(g(x)); }; }; 复制代码
这就是 组合(compose,以下将称之为组合),f 和 g 都是函数,x 是在它们之间通过“管道”传输的值。
组合看起来像是在饲养函数。你就是饲养员,选择两个有特点又遭你喜欢的函数,让它们结合,产下一个崭新的函数。
组合的用法如下:
var toUpperCase = function (x) { return x.toUpperCase(); }; var exclaim = function (x) { return x + '!'; }; var shout = compose(exclaim, toUpperCase); shout("send in the clowns"); //=\> "SEND IN THE CLOWNS!” 复制代码
柯里化 (Currying)
柯里化是对函数的封装,当调用函数时传递参数数量不足时,会返回一个新的函数,直到参数数量足够时才进行求值。
var add = function (x) { return function (y) { return x + y; }; }; var increment = add(1); var addTen = add(10); increment(2);// 3 addTen(2);// 12 复制代码
这里我们定义了一个 add 函数,它接受一个参数并返回一个新的函数。调用 add 之后,返回的函数就通过闭包的方式记住了 add 的第一个参数。
调用lodash curry 的:chestnut:
var curry = require('lodash').curry; function sum(x,y,z) { return x+y+z; } var sumCurry = curry(sum); sumCurry(1,2,3) == sumCurry(1,2)(3) == sumCurry(1)(2,3) == sumCurry(1)(2)(3) == 6 复制代码
模式匹配 (Pattern matching)
模式匹配是指可以为一个函数定义多个版本,通过传入不同参数来调用对应的函数。形式上有点像「方法重载」,但方法重载是通过传入参数类型不同来区分的,模式匹配没有这个限制。利用模式匹配,我们可以去掉函数中的「分支」(最常见的是 if),写出非常简洁的代码。
// 普通版本,需要在函数内部对参数进行判断 function fib(x) { if (x === 0) return 0; if (x === 1) return 1; return fib(x-1) + fib(x-2); } 复制代码
// 模式匹配版本。 // 由于 JavaScript 不支持模式匹配, // 下面代码只是作演示用途 var fib = (0) =\> 0; var fib = (1) =\> 1; var fib = (x) =\> fib(x-1) + fib(x-2); // 调用 fib(10); 复制代码
简单来说,pattern 就像是数学中的分段函数。通过使用 pattern matching,就可以对不同的参数定义不同的函数体。当调用函数的时候,可以通过对比实参和形参的模式就可以选择正确的函数体。
高阶函数 (Higher order function)
如果一个函数接受函数作为参数,或者返回值为函数,那么该函数就是高阶函数。
数组中常用到的高阶函数
Array.prototype.map() Array.prototype.reduce() Array.prototype.filter() Array.prototype.sort() Array.prototype.find() 复制代码
最后我们来看看一个混合使用这些函数的例子:
假设我要从 example.com?type=1&keyword=hello&test= 中提取查询字符串,过滤掉无效键值对,最后返回一个 Object 形式的结果,大概会有以下步骤:
-
type=1&keyword=hello&test ->
-
[ 'type=1', 'keyword=hello', 'test' ] ->
-
[ [ 'type', '1' ], [ 'keyword', 'hello' ], ['test'] ] ->
-
[ [ 'type', '1' ], [ 'keyword', 'hello' ] ] ->
-
{ type: 1, keyword: 'hello’ }
使用js的常用的数组以及字符串的函数实现
url .split('?')[1] .split('&') .map(v =\> v.split('=')) .filter(v =\> v.length === 2) .reduce((prev, next) =\> Object.assign(prev, { [next[0]]: next[1] }), {}); 复制代码
函数式编程的优点
总结一下,函数式编程具有以下四个优点:
一:代码简洁,开发快速
函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。
二:接近自然语言,易于理解
比如之前这样的表达式 (1 + 2) \* 3 - 4
,写成函数式语言
var result = subtract(multiply(add(1,2), 3), 4);
对它进行变形,可以得到另一种写法:
var result = add(1,2). Multiply(3). Subtract(4);
三:更方便的代码管理
函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。
四:易于"并发编程"
函数式编程不需要考虑"死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"(concurrency)。
以上是自己对函数式编程的一点拙见,请小哥哥小姐姐指正
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 函数式编程之数组的函数式编程
- 函数式编程 – 函数式编程如何影响您的编码风格?
- 纯函数:函数式编程入门
- 深入理解 Java 函数式编程,第 1 部分: 函数式编程思想概论
- 思想交融,Android中的函数式编程(2):什么是函数式编程
- 编程范式 —— 函数式编程入门
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。