关于函数式编程的思考(1)

1,824

作者:李英杰,美团金融前端团队成员。欢迎大家一起来探讨FP

题外话:只是单纯地谈谈个人对函数式编程的理解,欢迎大家来一起探讨。也不会提及高阶函数与范畴学的内容,只聊一些很入门的问题。函数式编程的优点这里也不做过多说明,会推荐大家看几篇文章,里面有很好的阐述。

斜体灰字部分是一些个人的吐槽和私货

目录

  1. 为什么函数式编程在前端复兴?

  2. 什么是函数式编程?

  3. 函数式编程如何思考问题?

  4. 函数式编程与面向对象编程有什么区别,各自的优缺点是什么?

  5. map,reduce是函数式编程吗?

  6. 推荐


1.我们来简单捋捋前端的发展。

  • 上古时代,全部都是静态页面
  • 有js了,大家可以在页面搞一搞简单的交互了
  • ajax来了,可以更加自由地做自己想做的事情了
  • 切图仔们不甘寂寞了,要搞事情,于是有了SPA

    好,我们先停在这里了。SPA来了,我们很开心,曾经的小人物可以尝试着做主人了,后端只要给接口我们就能呈现出一个完整的网站了。然而没过多久我们开始不开心了,能力越大责任越大,需要考虑的问题越来越多,问题也越来越复杂。其中一个比较关键的问题就是:数据与展示之间的关系我们该如何处理?

按时间顺序,列几个我了解的SPA框架

Extjs --- MVC
Angular --- MVVM
React/Redux --- 单向数据流(好尴尬,别人都是英文字母)
各种MVW框架都在努力地帮助我们理清数据与展示之间的关系。

发展到今天,展示方面React很流行了;数据处理方面,flux单向数据流的思想得到了大家的认同,基于flux思想的redux目前基本成为了复杂数据场景中react的标配。

无论是React还是Redux,都或多或少提到了函数式编程。是它们参考了函数式编程,还是在提出方案后发现与函数式编程比较搭,我们先不深究。可以看到的是,前端当前的发展阶段在一定程度契合了函数式编程的思维。
前端都是一群爱折腾的人,比较喜欢创造或建造一些东西。所以我们可能不是很喜欢大而全的东西,而是喜欢搭积木。
React说:我只负责view,你可以自由选择处理data的伙伴
Redux说:我只负责data,你可以自由选择处理view的伙伴
于是——无法抗拒
而它们提到的函数式编程,又像是一个已经半开的宝藏大门,在勾引着我们进去看看。必须承认,我也是看Redux入坑的。据我所知,像这样入坑的前端还不少。

函数式编程并不是新的编程范式,只不过最近才走进了前端的视野。大家可以思考一下这个问题,特别是对其感兴趣的同学——为什么直到今天前端才想要了解函数式编程,函数式编程是不是在所有场景下适用?
我们可以凭着兴趣和激情在所有场景下进行尝试,不过针对不同的场景选择合理的解决方案才是我们应该有的态度。比如:Redux作者也会说一句:或许你不需要Redux。

2.介绍完背景,可以正式发问了:什么是函数式编程?

前端方向上介绍函数式编程的文章和书籍对于定义给的都比较晦涩,或者干脆略过不写,只是写了些函数式编程的特点和优点。个人认为这对于前端同学理解函数式编程造成了一定的困扰。
最初学习的时候我不能理解:
    为什么相同输入对应同一输出
    为什么输入的参数不能改变
    为什么没有循环,只有递归
    为什么提到函数式编程就提数学
他们只是告诉我这个东西就该是这样子的。

在解释以上问题之前,先抛出另一个问题——什么是函数?
貌似是一个很白痴的问题,码农们怎么会不知道什么是函数,天天都在写函数的好不好。

再明确一下这个问题,“函数式编程”里的“函数”是什么?
这里面提到的“函数”是数学中的函数,不是编程中的函数。
数学中的函数是这样定义的:函数在数学中为两集合间的一种对应关系:输入值集合中的每项元素皆能对应唯一一项输出值集合中的元素。——选自维基百科

所以“函数”是这个样子的


也会是这个样子的

但不是这个样子的

function test(x, y) {
  console.log(x, y);
}

这是码农们的函数 :)

了解了这一点,结合以上两个函数的例子,大家可以思考一下刚刚提到的问题,应该很快就可以理解了。
    为什么相同输入对应同一输出
    为什么输入的参数不能改变
    为什么没有循环,只有递归
    为什么提到函数式编程就提数学

3.了解什么是函数编程,接下来就是我们如何用函数式编程解决问题了

  • 将实际问题转换为数学问题
  • 小函数组合成大函数,大函数组成更大的函数,直至解决问题 —— 这里函数仍是数学中的函数

用数学去解决问题有几点好处

  • 保证稳定的手段之一 ———— 数学是稳定的,关于数学为什么是稳定的,这里就不做说明了:)
  • 可推导 ———— 数学函数运算

推导在我们实际编程中有什么意义?
来看一个例子(这个例子是在其他书籍中看到的,只不过书籍的作者从其他角度分析了这个例子)

async({
  success: data => handleData(data),
  fail: () => alert('error)
})

async是一个异步函数,success和fail分别对应了该异步函数在成功和失败情况下的处理
从函数式的角度来思考一下如何优化
首先将程序中的函数用数学中的函数表示出来

然后我们来根据函数的内容来推导一下

如果对于任意输入,函数f, g的输出都一致,那么f,g就是可以互相替换的。

对于这个例子来说,我们就可以写成这个样子

async({
  success: handleData,
  fail: () => alert('error)
})

再举一个例子,大家非常熟悉的小学应用题
应用题:
小雨带着n个苹果出去玩,路过薛大叔家,薛大叔给了她n个苹果,薛大婶给了她1个苹果
问题1:小雨现在有多少个苹果
路过张砖头家,小雨给了张砖头n个苹果
问题2:小雨现在有多少个苹果?
已知:
add(x, y) = x + y;
sub(x, y) = x - y;

可以很快速地给出答案
问题1:f(x) = add(add(x,x),1)

let n = 10;
let result = add(add(n, 1), 1);

问题2:g(x) = sub(f(x), x)

let result2 = sub(result, n);

实际中,我们只是想知道问题2的结果。
问题1的存在可能有两个原因

  • 我们分解了开发过程
  • 最初我们想要得到问题1的结果,随着业务的发展,现在我们要得到问题2的结果

如果只是想要问题2的结果我们就可以进行优化了,用一下大家都很熟悉的四则运算
sub(f(x),x) = f(x) - x = x + x + 1 - x = x + 1 = add(x, 1)
也就是说
sub(f(x), x) 等价于 add(x, 1)
最终
g(x) = add(x, 1)

let n = 10;
let result2 = add(n, 1);

好的,这就是我们最终问题的答案了。

未完待续……


最后,团队为了招聘方便,整了个公众号,主要是一些招聘信息,团队信息,所有的技术文章在公众号里也可以看到,对了,如果你想去美团其他团队,我们也可以帮你内推哦 ~

二维码
二维码