彻底搞懂Functional Programming(二)

394 阅读4分钟

前言

今天会继续来聊聊 FP 的一些重要观念,而且会更偏向实际的做法,看看 Javascript 怎么结合昨天聊到的 First-class、HoF、pure function,并且落实「以 function 为主体」来写程式

✅ Imperative vs. Declarative

新手们通常看到这两个术语就直接转台了,如果你成功看到第二行,我会尽量讲清楚来报答你 (?)

Imperative

中文翻成命令式,是一个胼手胝足、努力向上的好青年,他做事的每一个步骤你都看在眼里,他关注的是细节,是「如何做到」。

const arr = [
    { id: 'item1', name: 'TV', price: 13500 },
    { id: 'item2', name: 'washing machine', price: 8200 },
    { id: 'item3', name: 'laptop', price: 25000 },
];

let totalPrice = 0;
arr.forEach(item => {
    if(item.price > 10000) {
        totalPrice += item.price;
    }
});

Declarative

中文翻成宣告式,是主管的类型,在公司的重要会议里面,他只会告诉你大方向及一些策略,他关注的是整体,是「要做什么」。

const arr = [
    { id: 'item1', name: 'TV', price: 13500 },
    { id: 'item2', name: 'washing machine', price: 8200 },
    { id: 'item3', name: 'laptop', price: 25000 },
];

const totalPrice = arr.filter(item => item.price > 10000)
                      .map(item => item.price)
                      .reduce((prev, curr) => prev + curr, 0);

✔两者差异

虽然 forEach 比起一般的 for 回圈来说,已经算是比较偏向宣告式了,当你看到 forEach 这个关键字的时候,你会立马知道:

  • forEach:我要跑回圈,而且会把阵列中每个元素跑过一遍

但比起联合军 (filter/map/reduce) 的成员来说,他们更宣告了一点,因为每个都更明确告诉你:

  • filter:我不只要跑回圈,还要做一个筛选的动作
  • map:我不只要跑回圈,还要做一个转换的动作
  • reduce:我不只要跑回圈,还要做一个整合的动作

谁好谁坏?

首先,没有好坏,虽然很多人比较想当主管,但那只是个拟人的形容,不要太入戏,两种都是程式写法的差异罢了,之前在 彻底掌握 Array(一)也讨论过,有些时候选择不同的写法都只是 trade-off 而已,没有绝对正确的答案。

不过正是因为没有正确答案,才更要去理解,这两种的擅长点各自在哪,才能够在对的地方发挥出来。

✅ Currying函数柯里化

currying 其实是一种数学方法,只是套用到 FP 来用,他的重点在于将 function 的「多个参数转成单个参数」。

直接举个例,原本要把两个数字相加,需要带两个参数进去:

const addNum = (x, y) => x + y;

使用 currying 方法转换之后会变成

const addNum = x => y => x + y;

有没有很眼熟?我们在彻底搞懂 Functional Programming (一)就看到这种写法了,但没有特别提 currying,因为我忘了当时主要在讲 HoF,但也透过同样一个范例了解到,其实我们能够使用 currying 方法,都多亏了 HoF 呢!

但这乍看之下完全是多此一举吧!要相加就丢两个参数啊,何必变成一个?

没错,如果我们真的只有「相加」一个函式,那大可用第一种方法就好,但当我们要开始写 FP,别忘记主体是 function,最小单位是 function,基本上做什么都要从 function 去排列组合

✔function 的拆解与组装

currying 绝对是 FP 非常重要的一步,因为有了 currying,我们才可以将 function 拆成比较小的积木,然后用这些积木去「组装」更多 function:

重点是透过 currying,我们可以创造小积木,然后把小积木组成中积木,最后再堆成一整个城堡 (生产线)。

const addNum = x => y => x + y;

const addFive = addNum(5);
const addTen = addNum(10);

addFive(3); // 8
addTen(3); // 13

✅ pipe & compose

「组合」function 的两大支柱,可以把两个或多个 function 组合成像生产线一样,A 执行完丢给 B,B 再接续执行。

直接上语法,如果要组合两个 function:

const pipe = (f, g) => (...args) => g(f(...args));
const compose = (f, g) => (...args) => f(g(...args));

pipe 是先左再右,compose 是先右再左,所以两者基本上是挑一个来用即可:

const pipe = (f, g) => (...args) => g(f(...args));
const compose = (f, g) => (...args) => f(g(...args));

const multiplyBy3 = num => num * 3;
const makePositive = num => Math.abs(num);
const multiplyBy3AndPositive = pipe(multiplyBy3, makePositive);
// 等同于
// const multiplyBy3AndPositive = compose(makePositive, multiplyBy3);

multiplyBy3AndPositive(-5); // 15

可以看到 multiplyBy3AndPositive 是一个完全透过「组装」制造的 function,有顺序地执行里生产线里面的两个 function,开始有 FP 的感觉啰!

这部份我们明天会有大量可以实战的案例!敬请期待

结语

今天介绍的东西其实满难的,一方面是因为 FP 本来就充满了许多数学元素,另一方面,当这些都只是概念的话,也很难跟实际的案例结合,但直接上实战又好容易死在路上 QQ

所以今天先介绍概念们,明天我们集合这两天的大成,试着应用在实战吧!