函数式编程简介

栏目: 编程语言 · 发布时间: 6年前

内容简介:最近在看函数式编程入门经典,自己总结一下,感兴趣的可以看一下函数式编程主要是基于数学函数和它的思想那么我们先复习一下数学中的函数即

最近在看函数式编程入门经典,自己总结一下,感兴趣的可以看一下

函数式编程

1. 简介

1.1 什么是函数式编程

函数式编程主要是基于数学函数和它的思想

那么我们先复习一下数学中的函数即

y = f(x)

即函数 f(x) 以 x 为参数,以 y 为结果,x 和 y 可以是任意的数字,这其中包含了几个关键点

  • 函数必须总是接收 一个 参数
  • 函数必须总是返回 一个值
  • 函数应该根据接收到的参数(例如 x),而不是外部环境运行
  • 对于一个给定的 x,只会输出唯一的 y

让我们从一个例子中来理解

// 一个计税函数
var percentValue = 5;
var calculateTax = (value) => {return value/100 * (100 + percentValue)};
复制代码

用数学中的函数来分析一下这个函数,首先看第三条,函数应该根据接收到的参数,而不是外部环境运行。这里的函数 calculateTax 依赖外部的 percentValue,因此,该函数在数学的意义上就不能称为函数,那么我们可以怎么将它改造成数学意义上的函数呢?

很简单

var calculateTax = (value,percentValue) => {return value/100 * (100 + percentValue)};
复制代码

现在这个函数就可以被称为一个真正的函数了。

现在让我们用简单的技术术语定义 函数式编程 :函数式编程是一种范式, 仅依赖输入输出就可以完成自身逻辑 的函数。这保证了当函数多次调用时依然可以返回相同的结果。函数不会 改变任何外部环境的变量

1.2 引用透明性

根据函数的定义,我们可以得出结论:所有函数对于相同的输入输出都讲返回相同的值。函数的这一属性被称为 引用透明性

来举个例子

var identity = (i) => { return i };
复制代码

我们定义了一个简单的函数,在函数的内部不依赖全局变量,它会简单的返回输入。现在假设它被应用于其他函数调用之间,如

sum(4,5) + identity(1);
复制代码

根据引用透明性的定义,我们可以把它转换为

sum(4,5) + 1;
复制代码

该过程被称为 替换模型 ,因此你可以直接替换函数的结果(主要是因为该函数的逻辑不依赖与其他全局变量)。

由于函数对于给定的输入结果返回相同的值,实际上我们就可以缓存它了,比如我们有一个函数 “factorial” 来计算阶乘。它接收一个参数以计算其阶乘,比如 5 的阶乘是 120。用户第二次输入 5 的阶乘时,由于引用透明性(对于相同输入返回相同结果)所以我们知道结果是 120,但是机器并不知道,我们需要让机器这个结果缓存下来以便以后调用直接返回结果,而不必再计算一遍。由此可以看出引用透明性和可缓存代码在函数式编程中的重要性。

1.3 函数式、声明式与抽象

函数式编程主张 声明式编程编写抽象的代码

什么是声明式编程

假设我们要打印出一个数组中的所有元素,我们可以采用如下方法

var array = [1,2,3];

for(let i = 0; i < array.length; i++){
    console.log(array[i])
}
复制代码

在这段代码中,我们精确的告诉了代码该如何做。如:获取数组长度,循环数组,用索引获取每个元素。这就是 命令式编程 。命令式编程主张告诉编译器 怎么做

来看另一种方式

var arr = [1,2,3];
arr.forEach((ele) => { console.log(ele) })
复制代码

上面这段代码我们移除了获取数组长度,循环数组,用索引获取数组元素等。我们只关心要做什么就行,获取数组长度,循环等都由机器帮我们做了,我们只需要关心 做什么 ,而不是 怎么做

函数式编程主张以抽象的方式创建函数,这些函数能够在代码的其他地方被重用。

1.4 纯函数

什么是纯函数?纯函数式对给定的输入返回相同输出的函数,例如

var double = (value) => value * 2;
复制代码

上面的 double 就是一个纯函数,因为对于相同的输入总是返回相同的输出。纯函数遵循引用透明性,因此我们可以直接用 10 代替 double(5)。所以纯函数的最了不起的地方是什么?我们来看一下

1.4.1 纯函数产生可测试的代码

不纯的函数具有副作用,下面以之前的计税函数以例进行说明

var percentValue = 5;
var calculateTax = (value) => {return value/100 * (100 + percentValue)};
复制代码

这个函数不是纯函数,主要因为它以来外部环境计算其逻辑,当外部环境改变时,它会影响结果。因此,纯函数的主要特征就是 不依赖与任何外部变量,也不应该改变任何外部变量 。如果改变了外部变量可能会引起其他函数的行为,即产生副作用,这是使系统的行为变得难以预测。

1.4.2 合理的代码

我们应该通过函数的名字推理出函数的作用,比如 double 函数

var double = (value) => value * 2;
复制代码

我们可以通过函数名轻易的推出这个函数会把给定的数值加倍,因此根据引用透明性,我们可以直接把 double(5) 替换成 10。还有一个例子,Math.max(3,4,5,6) 结果是什么?虽然我们只看到了函数的名字,但是我们很容易看出结果,我们看到实现了吗?并没有,为什么,就是因为 Math.max 是纯函数啊!!!

1.5 并发代码

纯函数允许我们并发的执行代码,因为纯函数 不会改变它的环境 ,这意味着我们根本不需要担心同步问题。当然,js 是单线程的,但是如果项目中使用了 webworker 来并发执行染污,该怎么办?或者有一段 Node 环境中的服务端代码需要并发的执行函数,又该怎么办呢?

// 非纯函数代码
let global = 'something'
let function1 = (input) => {
    // 处理 input
    // 改变 global
    global = "somethingElse"
}
let function2 = () => {
    if(global === "something"){
        // 业务逻辑
    }
}
复制代码

如果我们需要并发的执行 function1 和 function2,假设 function1 在 function2 之前执行,就会改变 function2 的执行结果,所以并发执行这些代码就会造成不良的影响,现在把这些函数改为纯函数。

let function1 = (input,global) => {
    // 处理 input
    // 改变 global
    global = "somethingElse"
}
let function2 = (global) => {
    if(global === "something"){
        // 业务逻辑
    }
}
复制代码

此处我们把 global 作为两个函数的参数,让它们变成纯函数,这样并发执行的时候就不会有问题了。

1.6 可缓存

既然纯函数对于给定的输入总能返回相同的输出,那么我们就能缓存函数的输出,例如

var double = (value) => {
    const cahce = {};
    if(!cache[value]){
        cache[value] = value * 2
    }
    return cahce[value];
}
复制代码

这个函数中,假设我们第一次输入 5,cache 中并没有,于是执行代码,由于闭包的存在,cache[5] = 10,第二次我们调用的时候,cache[5] 存在,所以直接 return 10,看到了吗?这就是纯函数的魅力!!!别忘记这是因为纯函数的引用透明性。

1.7 管道与组合

纯函数应该被设计为一次只做一件事,并且根据函数名就知道它所做的事情。

比如 linux 系统下有很多日常任务的命令,如 cat 用于打印文件内容,grep 用于搜索文件,wc 用于计算行数,这些命令一次只解决一个问题,但是我们可以用管道或组合来完成复杂的任务。假设我们需要在一个文件中找到一个特定的名称并统计它的出现次数,在命令行要输入如下指令

cat jsBook | grep -i “composing” | wc

上面的命令通过组合多个函数解决了我们的问题。组合不是 linux 命令独有的,它们是函数式编程范式的 核心

我们把它们称为函数式组合。来看一个 compose 函数的例子

var add1 = (value) =>{ return value+1 };
var double = (value) => {return value*2 };
var compose = (a,b) => {
    return (c) => {
        return a(b(c));
    }
}
var doubleAndAdd1 = compose(add1,double);
doubleAndAdd1(5) // 打印 5 * 2 + 1 = 11
复制代码

compose 函数返回一个函数,将 b 的结果作为 a 的参数,这里就是将 double 的结果作为 add1 的参数,来实现了函数的组合。

1.8 纯函数是数学函数

还记得我们之前的缓存函数吗,假设我们多次调用 double 对象,那么 cache 中就会变成这样

{
    1: 2,
    2: 4,
    3: 6,
    4: 8,
    5: 10
}
复制代码

假设我们设置 double 的输入范围限制为 1 - 5,而且我们已经为这个范围建立的 cache 对象,因此只要参照 cache 就能根据指定输入返回指定输出。也就是一一对应的关系。

那么数学函数的定义是什么呢?

在数学中,函数式一种输入集合和可允许的输出集合之间的关系,具有如下属性:每个输入都精确地关联一个输出。函数的输入称为参数,输出称为值。对于一个给定的函数,所有被允许的输入集合称为该函数的定义域,而被允许的输出集合称为值域。

上面的定义和纯函数完全一致,例如在 double 中,你能找到定义域和值域吗?当然可以!通过这个例子,可以很容易看到数学函数的思想被借鉴到函数式范式的世界

1.9 我们要做什么?

我们将通过学习,构建出一个 ES6-Functional 的函数式库,通过构建的过程,我们将理解如何使用 JavaScript 函数,以及如何在日常工作中应用函数式编程。

1.10 小结

这一节我们只是简单的介绍了函数式编程的概念,以及什么是纯函数,其中最重要的就是引用透明性。然后研究了几个短小的例子,通过例子来加深对函数式编程的理解。接下来我们将一步一步深入了解函数式编程


以上所述就是小编给大家介绍的《函数式编程简介》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

大规模Web服务开发技术

大规模Web服务开发技术

伊藤直也、田中慎司 / 李剑 / 电子工业出版社 / 2011-7 / 59.00元

Hatena是日本最大的Web服务提供商之一,它提供的服务包括关键字(类似于维基百科)、博客、相册等。《大规模Web服务开发技术》由伊藤直也、田中慎司所著,内容主要来自Hatena为学生们举行的暑期实习的课程,内容涵盖广泛,介绍了性能优化、分布式、算法、系统架构等各个方面,甚至还介绍了硬件的经济成本,是运维工程师们必不可少的参考书。书中还包括几个算法实习课题,介绍了压缩算法、全文搜索等算法的实现方......一起来看看 《大规模Web服务开发技术》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具