其实这篇文章有点标题党了,因为函数式编程是一个非常大的课题。而标题里的“真”听起来就有一股浓浓的中二气息。
没错,这篇文章是函数式装逼系列(1)(2)的进阶版。函数式编程是很火的,然而现在网上很多入门级的JS函数式编程教程(甚至书)都太水了,以为用用forEach
用用map
用用reduce
用用immutable
就叫函数式编程了。
Too young. Too simple.
本着搞一个大新闻的目的,我又开了这个无底天坑。当然一切都是从学习与娱乐的目的出发(这两件事其实并不冲突嘛),请勿评论本文中代码的实用价值。
瑞迪?黑喂狗!
0. 开篇
背景
要实现“真·函数式编程”,就要玩的彻底一点,必须挥刀自宫先禁用JavaScript里的大量语言特性,目前我能想到的有:
- 禁用
var
/let
,所有东西都用const
定义,也就是说无变量,强制immutable。 - 禁用分号,也就是不让“顺序执行”,解除过程式编程范式。
你不是不爱写分号吗,这次彻底不需要写了 - 禁用
if/else
,但允许用条件表达式condition ? expr1 : expr2
。 - 禁用
for/while/do-while
。 - 禁用
prototype
和this
来解除JS中的面向对象编程范式。 - 禁用
function
和return
关键字,仅用lambda表达式来编程(JS里叫箭头函数)。 - 禁用多参数函数,只允许使用单个参数,相当于强行curry化
但是为了更可读(其实也可读不到哪去),我们允许使用大量ES6的新特性,比如箭头函数(lambda表达式)、解构、参数解构等等。如果你想玩这些特性,建议使用最新版的Firefox,或者node.js 4.0或更高版本加上--harmony --harmony_modules --harmony_destructuring
等参数。
因为文中会用到一些ES6的特性,可能有的同学还不太清楚,所以这里花一小点篇幅简单的挑重点介绍一下:
箭头函数
其他语言里面一般叫做lambda表达式,其实我个人当然是喜欢这个名字,但是因为ES6的语言规范里就把它管叫箭头函数,自然文中还是会尽量这么说。
箭头函数的基本定义方式是:
1 2 3 |
(参数列表) => { 函数体 } |
当只有一个参数的时候,可以省略括号,写成
1 2 3 |
参数名 => { 函数体 } |
当函数体是一个表达式(而不是段落)的时候,可以隐式return
,写成
1 |
参数名 => 返回表达式 |
由于我们的“真·函数式编程”是禁用过程式编程的,不存在段落,于是你可以见到下文中几乎所有的箭头函数都是最简单的形式,例如x => x * 2
。
箭头函数可以返回函数,并且在返回函数的时候,它也可以隐式return
,因此可以像haskell一样构建curry风格的函数,如
1 |
const add = x => y => x + y |
用传统的风格来“翻译”上面的add
函数,就是
1 2 3 4 5 |
function add(x) { return function(y) { return x + y } } |
调用的时候自然也是使用curry风格的逐个传参add(5)(3)
,结果就是8。
解构
解构是现代编程语言当中一个非常非常甜的语法糖,有时候我们为了实现多返回值,可能会返回一个数组,或者一个KV,这里以数组为例
1 |
const pair = a => b => [a, b] |
我们可以用解构一次性将数组中的元素分别赋值到多个变量如
1 |
const [a, b] = pair('first')('second') // a是'first',b是'second' |
参数结构就是在定义函数参数的时候使用结构
1 2 3 |
const add = ([a, b]) => a + b add([5, 3]) |
在add
函数里面,数组[5, 3]
可以被自动解构成a
和b
两个值。数组解构有一个高级的“剩余值”用法:
1 |
const [first, ...rest] = [1, 2, 3, 4, 5] // first是1,rest是[2, 3, 4, 5] |
可以把“剩余值”解构到一个数组,这里叫rest
。
关于解构语法的更多趣闻,可以看看我之前的一篇博客。
OK,前戏就到这里,下面进入主题。
1. 实现循环
实现for循环遍历数组
命令式编程当中,循环是最基本的控制流结构之一了,基本的for
循环大概是这样: