JavaScript 函数编程(一)
函数式编程的简单术语
什么是函数式编程?为什么很重要?
数学中的函数
函数F,它以X为参数,并返回输出Y。 在定义中有重要的含义:
- 函数必须有参数。
- 函数必须始终返回一个值。
- 函数只能对其接收参数(即X)起作用,而不是外部。
- 对于给定的X,将只有一个Y。
函数式编程技术主要基于数学函数及其思想,我们将会看到数学函数的想法以及如何使用它们来理解函数式编程。
想象一下,我们必须编写一个执行税收计算的函数。
| |
以上的功能calculateTax完全符合我们要做的。 看起来整洁,不是吗? 我们暂停一下,就我们的数学定义分析上述功能。 我们的数学函数术语的关键之一是函数逻辑不应该依赖于外部世界。 在我们上面定义的函数calculateTax中,我们使函数取决于全局变量percentValue。 因此,我们创建的上述函数在数学意义上不能被称为真正的函数。 所以我们来解决一下。
修复很简单:我们只需要将percentValue作为我们的函数参数:
| |
现在我们的函数calculateTax可以被称为真正的函数。 我们刚刚在calculateTax函数中消除了全局变量访问。 删除函数内的全局变量访问使得测试变得容易。 函数式编程是一个范例,我们将在其中创建功能,通过依赖于其输入来实现其逻辑。 这样可以确保在多次调用时函数将返回相同的结果。 该功能也不会改变外部任何数据,导致可高速缓存和可测试的代码库。
引用透明(Referential Transparency)
所有的函数都将为相同的输入返回相同的值,而把这个特征称为引用透明。
| |
现在这个过程称为替代模型(Substitution model),因为您可以直接用返回值替换函数的结果(主要是因为函数的逻辑不依赖于其他全局变量)。
这可以产生并行代码和缓存。
想象一下,使用这个模型,您可以轻松地使用多个线程运行上述函数,甚至不需要同步!为什么?同步的原因来自线程在并行运行时不应该对全局数据采取行动的事实。
而且由于函数将为给定的输入返回相同的值,所以实际上可以缓存它!例如,假设有一个叫做“factorial”的函数,它计算给定数字的阶乘。 我们都知道’5’的阶乘将是’120’。如果用户第二次调用“5”的“factorial”怎么办?如果“因子”函数遵循参照透明度,那么我们知道结果将像以前一样是“120”(并且只取决于输入参数)。考虑到这个特征,我们可以缓存我们的“factorial”函数的值。因此,如果第二次调用“factorial”,输入为“5”,我们可以返回缓存的值而不是再次计算。
强制性,声明性,抽象性
假设你有一个列表或数组,并想要遍历数组并将其打印到控制台。 代码可能如下所示:
| |
工作正常但是,在上述解决问题的方法中,我们正在准确地告诉我们如何做到这一点。例如,我们已经编写了一个隐含的for循环,并使用索引计算数组长度并打印项目。这里的任务是什么? “打印数组元素”对吧?但是看起来我们正在告诉编译器该怎么做。在这种情况下,我们告诉“获取数组长度,循环我们的数组,使用索引获取数组的每个元素”。我们称之为“命令式”解决方案。
命令编程就是告诉编译器“怎么做”事情。
我们现在将切换到硬币的另一面,声明式编程。在声明式编程中,我们将要告诉编译器需要做什么,而不是“如何”的部分。 “如何”部分被抽象为共同的功能(这些功能被称为高阶函数,我们将在后面介绍)。现在我们可以使用内建的forEach函数来迭代数组并进行打印。
| |
上面的代码段打印完全相同的输出。 但是在这里我们已经删除了“如何”的部分,如“获取数组长度,循环我们的数组,使用索引获取数组的每个元素”等。我们使用了一个抽象的函数,它实现了“如何”部分。
函数式编程是以抽象的方式创建函数,可以由代码的其他部分重用。
纯函数(Pure Functions)
纯函数是为给定输入返回相同输出的函数。
| |
上述函数“double”是一个纯函数,只是因为给定一个输入,它将始终返回相同的输出。 使用输入5调用总是将结果返回为10! 纯函数服从引用透明。 因此,我们可以用10替换双(5),没有任何犹豫。
- 纯函数产生可测试代码
- 纯函数产生合理的代码
并行代码
纯函数允许我们并行运行代码。 由于纯函数不会改变其任何环境,所以这意味着我们不需要担心所有的同步!
缓存
由于纯函数总是为给定的输入返回相同的输出,我们可以缓存功能输出。 想象一下,我们有一个功能耗时的计算,我们将这个函数命名为longRunningFunction
| |
如果longRunningFunction函数是一个纯函数,那么我们知道对于给定的输入,它将返回给我们相同的输出!想象一下,我们有一个对象,它保留longRunningFunction的所有函数调用结果
| |
管道和组合
使用纯函数,我们将在该函数中仅做一件事情。 我们已经看到,纯函数将如何通过查看其名称来作为对该功能的自我理解。 纯函数的设计应该只有一件事。 只做一件事情就是一个UNIX哲学; 我们将在实现我们的纯函数时遵循相同的规定。 在UNIX和LINUX平台中有许多命令,用于日常任务。 例如,我们使用cat打印文件的内容,grep来搜索文件,wc来计算行数等。这些命令一次解决一个问题。 但是我们可以组合或管理这些复杂的任务。
组合不仅仅是UNIX / LINUX命令行的唯一,而是函数式编程范例的核心。 我们称之为函数组合。