函数式编程就是用函数写程序,当然这是一句废话。我相信大家都知道什么是用函数写程序。但是大家可能不知道如何通过函数去构造复杂的、大型的、可扩展的、可测试,复用率高,安全性强的、有数学证明的程序。
面向对象编程以现实世界为原型去投影程序的世界,适合对事务进行抽象、组织、分解;函数式编程则研究的是如何更好的进行计算。
计算是计算机最重要的能力。让计算没有副作用、让计算可以并行、让计算可以被数学证明、让计算更安全、让计算最大程度被复用,这些都是函数式编程的目标。
因此作为一个出色的Java工程师,是有必要学习一下函数式编程的。
关联面试题:
- Optional的作用?
- FunctionalInterface在做什么?
- 说说你理解的函数式编程?
- 解释下什么是Monad?
- 如何实现管道和流?
流计算的类型设计?
请你先看一下这个例子。一开始,我们构造了一个Optional的类型。
@Test
public void test_optional(){
var result = Optional.of(100)
.map(x -> x*x)
.map(x -> x / 100);
}
我们用map进行映射,求了平方,好除以100。请你仔细观察类型的变化,看看有没有什么发现。
上图中是类型的变化,数据源的类型是Optional。中间map函数运算时,进行了拆箱,实际操作的类型是整数到整数的函数。最终的结果,依然是Optional。
那么家来请你思考两个问题:
- 为什么中间的运算不用Optional类型?
- 为什么两端的数据用Optional类型?
道理其实很简单。在我们进行流计算的这个过程当中,我们期望的是类型的统一、以及类型的安全。Optional类型支持空值,准确说,是empty/undefined类型,因此使用起来更加安全。
空值和空值进行操作,没有抛异常,而是继续返回空值。这让我们无论得到什么值,计算都可以继续。
if(!optionalValue.isPresent()) {
return Optional.empty();
}
// 计算逻辑
这样的写法不可取。这样会产生大量的判断,这样的写法应该被封装起来。只有这样才能最大程度的减少程序员的心智负担。
看下下面的例子:
@Test
public void test_udef(){
Optional<Integer> x= Optional.empty();
var y = x.map(a -> a * a);
System.out.println(y);
}
在上面的程序当中map内部实现了逻辑,对于empty的值,并不会进入计算逻辑,而是直接以empty的值返回,这样大大节省了用户进行判断的时间。
流计算之所以要设计这些中间的类型,是为了封装错误、避免问题、组织一致性的计算过程、以及惰性的求值过程……
Monad
上面我们描述的流计算的类型设计,实际上就是Monad模式。Monad有非常多的定义。比如说,数学上的。我们先给一个最简单的定义,是从设计模式上去思考。
- 一个Monad设计模式有3个显著的特点:
- 一个泛型的构造函数。比如: Optional
不改变泛型类型的运算操作,内部是非泛型计算:例如:Optional map(T -> R)
泛型类型不变。 比如可以是Optional到Optional,但还是Optional类型。
你可能会说,这样的设计究竟有什么用?这个设计其实非常有价值。比如说你可以思考一下Stream的设计。
@Test
public void test_stream(){
var result = Stream.of("Hello", "World")
.map(x -> x.length());
}
泛型类型不变是构造流计算的基石。Stream 是一类事物,这一类事物无论作用在怎样的操作上,最终结果还是Stream。这样map、filter、reduce等操作就能作用在任何类型的流上。
也是Monad的值。Moand设计模式中,除了要设计bind方法,还需要构造函数(或工厂方法)。这个构造函数用于构造Monad类型,比如Stream.of等等。
实战场景
管道和流计算是需要Monad的一个非常重要的场景。在我们实现业务逻辑的时候,抽象到非常领域化的逻辑也可以使用Monad。尽管通常这样的设计模式我们会被称之为连贯操作。但是设计的好的连贯操作。应该是Monad,因为Monad的拥有数学的证明,严密可靠。
比如:
SearchResult<Order> result = Search.