什么是函数式编程?手摸手教小学妹实操,超实用操作指南

2021/7/3 20:52:39

本文主要是介绍什么是函数式编程?手摸手教小学妹实操,超实用操作指南,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

函数式编程意味着使用函数来创建干净且可维护的软件的最佳效果。本文通过 JavaScript 和 Java 中的实际示例说明了函数范式背后的概念。

函数式编程从一开始就一直是软件开发的潮流,但在现代具有新的重要性。本文着眼于函数式编程背后的概念,并通过 JavaScript 和 Java 中的示例提供实际理解。

函数式编程定义

函数是代码组织的基础;它们存在于所有高阶编程语言中。一般来说,函数式编程意味着使用函数来创建干净且可维护的软件的最佳效果。更具体地说,函数式编程是一组编码方法,通常被描述为一种编程范式。

函数式编程有时被定义为与面向对象编程 (OOP) 和过程式编程相对立。这是一种误导,因为这些方法并不是相互排斥的,而且大多数系统倾向于同时使用这三种方法。

函数式编程在某些情况下提供了明显的好处,它在许多语言和框架中被大量使用,并且在当前的软件趋势中很突出。它是一个有用且强大的工具,应该成为每个开发人员的概念和语法工具包的一部分。

纯函数

函数式编程的理想是所谓的纯函数。纯函数是一种结果仅取决于输入参数的函数,并且其操作不会引发副作用,即除了返回值之外不产生任何外部影响。

纯函数的美妙之处在于其架构的简单性。因为一个纯函数被简化为只有参数和返回值(即它的 API),所以它可以被视为一个复杂的死胡同:它与它运行的外部系统的唯一交互是通过定义的 API。

这与 OOP 形成对比,在 OOP 中,对象方法被设计为与对象(对象成员)的状态交互,与过程式代码形成对比,后者通常从函数内部操纵外部状态。

推荐的白皮书

推荐学习资料

然而,在实际实践中,函数往往最终需要与更广泛的上下文进行交互,正如 React 的useEffect钩子所证明的那样。

不变性

函数式编程哲学的另一个原则是不在函数外修改数据。实际上,这意味着避免修改函数的输入参数。相反,函数的返回值应该反映完成的工作。这是一种避免副作用的方法。当函数在更大的系统中运行时,它可以更容易地推断函数的影响。

一级功能

除了纯函数理想之外,在实际编码实践中,函数式编程取决于一等函数。一等函数是被视为“事物本身”的函数,能够独立存在并被独立处理。函数式编程试图利用语言支持将函数用作变量、参数和返回值来创建优雅的代码。

因为一流的函数是如此灵活和有用,即使是像 Java 和 C# 这样的强 OOP 语言也已经转向合并一流的函数支持。这就是 Java 8 支持 Lambda 表达式背后的推动力。

另一种描述一流函数的方法是将函数作为数据。也就是说,可以像任何其他数据一样将第一类函数分配给变量。当您编写时,let myFunc = function(){}您正在使用一个函数作为数据。

高阶函数

接受函数作为参数或返回函数的函数称为高阶函数——对函数进行运算的函数。

近年来,JavaScipt 和 Java 都添加了改进的函数语法。Java 添加了箭头运算符和双冒号运算符。JavaScript 添加了箭头运算符。这些运算符旨在使定义和使用函数更容易,尤其是作为匿名函数内联。匿名函数是在没有给定引用变量的情况下定义和使用的函数。

函数式编程示例:集合

也许函数式编程最突出的例子是处理集合。这是因为能够跨集合中的项目应用功能块是纯函数思想的自然契合。

考虑清单 1,它利用 JavaScriptmap()函数将数组中的字母大写。

清单 1. 在 JavaScript 中使用 map() 和匿名函数

让字母 = ["a", "b", "c"];
控制台信息(信件.地图((x)=> x.toUpperCase())); // 输出 ["A", "B", "C"]

这种语法的美妙之处在于代码非常集中。不需要命令式管道,例如循环和数组操作。这段代码清楚地表达了正在做的事情的思考过程。

使用 Java 的箭头运算符可以实现相同的目的,如清单 2 所示。

清单 2. 在 Java 中使用 map() 和匿名函数

导入 java.util.*; 
导入 java.util.stream.Collectors;
导入静态 java.util.stream.Collectors.toList;
//...
Listlower = Arrays.asList("a","b","c");
System.out.println(lower.stream().map(s -> s.toUpperCase()).collect(toList())); // 输出 ["A", "B", "C"]

清单 2 使用 Java 8 的流库来执行大写字母列表的相同任务。请注意,核心箭头运算符语法实际上与 JavaScript 相同,它们做同样的事情,即创建一个接受参数、执行逻辑并返回值的函数。(重要的是要注意,如果这样定义的函数体周围缺少大括号,则自动给出返回值。)

继续 Java,考虑清单 3 中的双冒号运算符。该运算符允许您引用类上的方法:在本例中,toUpperCase是 String 类上的方法。清单 3 的作用与清单 2 相同。不同的语法适用于不同的场景。

清单 3. Java 双冒号运算符

// ...
List upper = lower.stream().map(String::toUpperCase).collect(toList());

在上面的所有三个示例中,您可以看到高阶函数在起作用。map()两种语言中的函数都接受函数作为参数。

换句话说,您可以将函数传递给其他函数(在 Array API 中或以其他方式)作为函数接口。提供者函数(使用参数函数)是通用逻辑的插件。

这看起来很像 OOP 中的策略模式(实际上,在 Java 中,在幕后生成了具有单个方法的接口),但是函数的紧凑性使得组件协议非常紧凑。

作为另一个示例,请考虑清单 4,它在 Node.js 的 Express 框架中定义了一个路由处理程序。

清单 4. Express 中的函数式路由处理程序

var express = require('express');
var app = express();
app.get('/', function (req, res) {
  res.send('One Love!');
});

清单 4 是函数式编程的一个很好的例子,因为它允许对映射路由和处理请求和响应所需的确切定义进行清晰的定义——尽管可能有人认为在函数体内操作响应对象是一种副作用.

咖喱函数

现在考虑返回函数的函数的函数式编程概念。与作为参数的函数相比,这种情况不太常见。清单 5 有一个来自常见 React 模式的示例,其中链接了粗箭头语法。

清单 5. React 中的柯里化函数

handleChange = 字段 => e => {
e.preventDefault();
// 处理事件
}

面的目的是创建一个事件处理程序,它将接受有问题的字段,然后是事件。这很有用,因为您可以将其handleChange应用于多个字段。简而言之,同一个处理程序可用于多个字段。

清单 5 是一个柯里化函数的示例。“咖喱函数”这个名字有点令人沮丧。它尊重一个人,这很好,但它没有描述这个概念,这是令人困惑的。无论如何,我们的想法是,当您有返回函数的函数时,您可以将调用链接在一起,这比创建具有多个参数的单个函数更灵活。

在调用这些类型的函数时,您会遇到独特的“链式括号”语法:handleChange(field)(event).

大范围编程

前面的示例提供了在重点上下文中对函数式编程的动手理解,但函数式编程旨在为大型编程带来更大的好处。换句话说,函数式编程旨在创建更清洁、更具弹性的大型系统。

很难提供这方面的例子,但一个真实的例子是 React 推广功能组件的举措。React 团队已经注意到,组件的更简洁的功能风格提供了随着界面架构变得更大而复合的好处。

另一个大量使用函数式编程的系统是ReactiveX。建立在 ReactiveX 使用的那种事件流上的大型系统可以从解耦的软件组件交互中受益。Angular 全面采用 ReactiveX (RxJS) 作为对这种能力的认可。

变量范围和上下文

最后,作为范式不一定是函数式编程的一部分,但在进行函数式编程时需要注意的一个问题是变量范围和上下文。

在 JavaScript 中,上下文特指this关键字解析的内容。在 JavaScript 箭头运算符的情况下,this指的是封闭上下文。使用传统语法定义的函数接收其自己的上下文。DOM 对象上的事件处理程序可以利用这一事实来确保this关键字引用正在处理的元素。

范围是指变量的范围,即哪些变量是可见的。对于所有 JavaScript 函数(胖箭头函数和传统函数)以及 Java 箭头定义的匿名函数,作用域是封闭函数体的作用域——尽管在 Java 中,只有那些有效的最终变量才能成为访问。这就是为什么这些函数被称为闭包。该术语意味着函数被包含在其包含范围内。

记住这一点很重要:这样的匿名函数可以完全访问作用域中的变量。内部函数可以对外部函数的变量进行操作。这可以被认为是非纯函数的副作用。



这篇关于什么是函数式编程?手摸手教小学妹实操,超实用操作指南的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程