跳到主要内容

函数式编程

什么是函数式编程

假设我们有这么个需求,我们登记了一系列人名存在数组中,现在需要对这个结构进行一些修改,需要把字符串数组变成一个对象数组,方便后续的扩展,并且需要把人名做一些转换:

['john-reese', 'harold-finch', 'sameen-shaw'] 
// 转换成
[{name: 'John Reese'}, {name: 'Harold Finch'}, {name: 'Sameen Shaw'}]

用传统的编程思路,我们一上来就可以撸代码,临时变量,循环走起来:

const arr = ['john-reese', 'harold-finch', 'sameen-shaw'];
const newArr = [];
for (let i = 0, len = arr.length; i < len ; i++) {
let name = arr[i];
let names = name.split('-');
let newName = [];
for (let j = 0, naemLen = names.length; j < naemLen; j++) {
let nameItem = names[j][0].toUpperCase() + names[j].slice(1);
newName.push(nameItem);
}
newArr.push({ name : newName.join(' ') });
}
return newArr;

完成,这几乎是所有人下意识的编程思路,完全的面向过程。你会想我需要依次完成:

  • 定义一个临时变量 newArr。
  • 我需要做一个循环。
  • 循环需要做 arr.length 次。
  • 每次把名字的首位取出来大写,然后拼接剩下的部分。
  • ……
  • 最后返回结果。

这样当然能完成任务,最后的结果就是一堆中间临时变量,光想变量名就让人感到崩溃。同时过程中掺杂了大量逻辑,通常一个函数需要从头读到尾才知道它具体做了什么,而且一旦出问题很难定位。

一直以来,我也没觉得这样编程有什么问题,直到我遇到了函数式编程。我们来看一看一个 FPer 会如何思考这个问题:

1、我只需要一个函数能实现从 String 数组 到 Object 数组 的转换:

convertNames :: [String] -> [Object]

2、这里面涉及到一个 String -> Object 的转换,那我需要有这么个函数实现这种转换:

convert2Obj :: String -> Object

3、至于这种转换,可以轻松想到需要两个函数完成:

  • capitalizeName:把名称转换成指定形式
  • genObj:把任意类型转换成对象

4、如果再细想一下,capitalizeName 其实也是几个方法的组合(split, join, capitalize),剩下的几个函数都是非常容易实现的。

最后我们把这些函数组合起来,就可以得到我们想要的结果了:

const capitalize = x => x[0].toUpperCase() + x.slice(1).toLowerCase();

const genObj = curry((key, x) => {
let obj = {};
obj[key] = x;
return obj;
})

const capitalizeName = compose(join(' '), map(capitalize), split('-'));
const convert2Obj = compose(genObj('name'), capitalizeName)
const convertName = map(convert2Obj);

convertName(['john-reese', 'harold-finch', 'sameen-shaw'])

我特别喜欢用流水线去形容这种工作,把输入当做原料,把输出当做产品,数据可以不断的从一个函数的输出可以流入另一个函数输入,最后再输出结果,这不就是一套流水线嘛?

Monad 是什么?

简单说,Monad就是一种设计模式,表示将一个运算过程,通过函数拆解成互相连接的多个步骤。你只要提供下一步运算所需的函数,整个运算就会自动进行下去。其实就是 Java Stream 的流式编程那些东西

Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());

References