函数式编程详解

Hello,大家好,这里是翊君@周一通勤电台频道。本频道将会从一个知识点出发,让您的通勤既不会无聊也不会疲惫。

本文一共约1250字,阅读时长大概等于一首陶喆的《沙滩》

【周一通勤电台】第1期 带你认识函数式编程

1. 定义

函数式编程意味着创造干净和可维护的软件的最佳效果。它是一种编程范式

它与面向对象编程(OOP)、面向过程编程(OPP)并不对立。通常与它放在一起讨论的还有声明式编程、命令式编程。

2. 特点

2.1 函数是一等公民 (first class)

其实,所谓一等公民的函数跟其他数据类型一样,你可以把它们塞进数组,用作传参,赋值给变量。

当你写下let myFun = function(){}时,你就是把一个函数作为数据。

再看下面这个例子:

var print = function(i){
    console.log(i);
};
[1,2,3].forEach(print);

print变量是一个函数,但是可以作为forEach函数的参数。

2.2 纯函数

函数式编程的理想就是所谓的纯函数。

输入相同的情况下,纯函数的输出不变,并且不会产生任何副作用

  1. 输出不变
// 纯
var checkNum = function(num) {
  var minimum = 21;
  return num >= minimum;
};

// 不纯
var minimum = 21;
var checkNum = function(num) {
  return num >= minimum;
};

在不纯的版本中,函数的返回值依赖于minimum这个外部元素,不是不变的。

再比如说,slice和splice两个函数。slice符合纯函数的定义,因为对于相同的输入,它可以保证相同的输出。但是splice做不到。

var array = [1,2,3,4,5];

array.slice(0,3);// [1,2,3]

array.slice(0,3);// [1,2,3]

array.splice(0,3);// [1,2,3]

array.splice(0,3);// [4,5]

  1. 副作用

只要跟函数外部环境发生的交互就是副作用。

常见的例子:

  • 数据库插入
  • 打印/log
  • 更改文件系统/读取文件
  • 访问系统状态

副作用之所以会让纯函数不纯,就是因为函数需要与外部打交道。如果你的程序大多由纯函数构成,那么程序的测试和调试会非常方便。

在OOP中,对象方法被设计为与对象的状态(对象成员)进行交互,与OPP代码相反,在OPP代码中,外部状态经常从函数中被操作。

然而,在实际操作中,函数最终往往需要与更广泛的上下文进行交互。

3. 工具之一 柯里化 (curry)

柯里化的逻辑:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

一个简单的例子:


// 柯里化之前
function add(x, y) {
  return x + y;
}

add(1, 2) // 3
// 柯里化之后
function add(y) {
  return function (x) {
    return x + y;
  };
}
//或者采用箭头函数的写法
//const add = x => y => x + y;
add(1)(2) // 3

这里我们定义了一个add函数,它接受一个参数并返回一个新的函数。

调用了add之后,返回的函数就通过闭包的方法记住了add的第一个参数。

当然,我们可以使用curry帮助函数使得这类函数的定义和调用更加容易。

4. Java中函数式编程的例子

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

  1. 在Java中使用map()和一个匿名函数
import java.util.*; 
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
//...
List lower = Arrays.asList("a","b","c");
System.out.println(lower.stream().map(s -> s.toUpperCase()).collect(toList())); 

这段干净简洁的Java代码实现了将一个数组中的字母大写化的功能,创建了一个接受参数、执行逻辑并返回数值的函数。

这种语法的好处是,代码是紧密集中的。不需要诸如循环和数组之类的操作。

值得注意的是,如果这样定义的函数体周围没有大括号,那么返回值就会自动给出。

  1. Java双冒号操作符
// ...
List upper = lower.stream().map(String::toUpperCase).collect(toList());

在本例中,我们做了与例1一样的事情。不同的语法在不同的情况下都能派上用场。

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

感谢收看本期的翊君@周一通勤电台。如果你觉得还不错的话,快给我三连支持一下吧,咱们下期不见不散呐。


原文始发于微信公众号(下班的思想家):【周一通勤电台】第1期 带你认识函数式编程

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/20164.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!