MonadMonad 本身也是一种 Functor。Monad 的目的在于描述副作用。
函数的副作用与组合方式清单 1 给出了一个简单的函数 increase。该函数的作用是返回输入的参数加 1 之后的值。除了进行计算之外,还通过 count++来修改一个变量的值。这行语句的出现,使得函数 increase 不再是纯函数,每次调用都会对外部环境造成影响。
清单 1. 包含副作用的函数1
2
3
4
5
6
| int count = 0;
int increase(int x) {
count++;
return x + 1;
}
|
清单 1 中的函数 increase 可以划分成两个部分:产生副作用的 count++,以及剩余的不产生副作用的部分。如果可以通过一些转换,把副作用从函数 increase 中剥离出来,那么就可以得到另外一个纯函数的版本 increase1,如清单 2 所示。对函数 increase1 来说,我们可以把返回值改成一个 Vavr 中的 Tuple2<Integer, Integer> 类型,分别包含函数原始的返回值 x + 1 和在 counter 上增加的增量值 1。通过这样的转换之后,函数 increase1 就变成了一个纯函数。
清单 2. 转换之后的纯函数版本1
2
3
| Tuple2<Integer, Integer> increase1(int x) {
return Tuple.of(x + 1, 1);
}
|
在经过这样的转换之后,对于函数 increase1 的调用方式也发生了变化,如清单 3 所示。递增之后的值需要从 Tuple2 中获取,而 count 也需要通过 Tuple2 的值来更新。
清单 3. 调用转换之后的纯函数版本1
2
3
4
| int x = 0;
Tuple2<Integer, Integer> result = increase1(x);
x = result._1;
count += result._2;
|
我们可以采用同样的方式对另外一个相似的函数 decrease 做转换,如清单 4 所示。
清单 4. 函数 decrease 及其纯函数版本1
2
3
4
5
6
7
8
| int decrease(int x) {
count++;
return x - 1;
}
Tuple2<Integer, Integer> decrease1(int x) {
return Tuple.of(x - 1, 1);
}
|
不过需要注意的是,经过这样的转换之后,函数的组合方式发生了变化。对于之前的 increase 和 decrease 函数,可以直接组合,因为它们的参数和返回值类型是匹配的,如类似 increase(decrease(x)) 或是 decrease(increase(x)) 这样的组合方式。而经过转换之后的 increase1 和 decrease1,由于返回值类型改变,increase1 和 decrease1 不能按照之前的方式进行组合。函数 increase1 的返回值类型与 decrease1 的参数类型不匹配。对于这两个函数,需要另外的方式来组合。
在清单 5 中,compose 方法把两个类型为 Function<Integer, Tuple2<Integer, Integer>> 的函数 func1 和 func2 进行组合,返回结果是另外一个类型为 Function<Integer, Tuple2<Integer, Integer>> 的函数。在进行组合时,Tuple2 的第一个元素是实际需要返回的结果,按照纯函数组合的方式来进行,也就是把 func1 调用结果的 Tuple2 的第一个元素作为输入参数来调用 func2。Tuple2 的第二个元素是对 count 的增量。需要把这两个增量相加,作为 compose 方法返回的 Tuple2 的第二个元素。
清单 5. 函数的组合方式1
2
3
4
5
6
7
8
9
| Function<Integer, Tuple2<Integer, Integer>> compose(
Function<Integer, Tuple2<Integer, Integer>> func1,
Function<Integer, Tuple2<Integer, Integer>> func2) {
return x -> {
Tuple2<Integer, Integer> result1 = func1.apply(x);
Tuple2<Integer, Integer> result2 = func2.apply(result1._1);
return Tuple.of(result2._1, result1._2 + result2._2);
};
}
|
清单 6 中的 doCompose 函数对 increase1 和 decrease1 进行组合。对于一个输入 x,由于 increase1 和 decrease1 的作用相互抵消,得到的结果是值为 (x, 2) 的对象。
清单 6. 函数组合示例1
2
3
| Tuple2<Integer, Integer> doCompose(int x) {
return compose(this::increase1, this::decrease1).apply(x);
}
|
可以看到,doCompose 函数的输入参数和返回值类型与 increase1 和 decrease1 相同。所返回的结果可以继续使用 doCompose 函数来与其他类型相同的函数进行组合。 |