JS 标签

在前面几篇介绍了函数式比较重要的一些概念和如何用函数组合去解决相对复杂的逻辑。是时候开始介绍如何控制副作用了。

数据类型

我们来看看上一篇最后例子:

1
2
3
4
5
6
7
const split = curry((tag, xs) => xs.split(tag))
const reverse = xs => xs.reverse()
const join = curry((tag, xs) => xs.join(tag))

const reverseWords = compose(join(''), reverse, split(''))

reverseWords('Hello,world!');

这里其实reverseWords还是很难阅读,你不知道他入参是啥,返回值又是啥。你如果不去看一下代码,一开始在使用他的时候,你应该是比较害怕的。 “我是不是少传了一个参数?是不是传错了参数?返回值真的一直都是一个字符串吗?”。这也是类型系统的重要性了,在不断了解函数式后,你会发现,函数式编程和类型是密切相关的。如果在这里reverseWords的类型明确给出,就相当于文档了。

但是,JavaScript是动态类型语言,我们不会去明确的指定类型。不过我们可以通过注释的方式加上类型:

1
2
// reverseWords: string => string
const reverseWords = compose(join(''), reverse, split(''))

上面就相当于指定了reverseWords是一个接收字符串,并返回字符串的函数。

JS 本身不支持静态类型检测,但是社区有很多JS的超集是支持类型检测的,比如Flow还有TypeScript。当然类型检测不光是上面所说的自文档的好处,它还能在预编译阶段提前发现错误,能约束行为等。

当然我的后续文章还是以JS为语言,但是会在注释里面加上类型。

我们都知道单一职责原则,其实面向对象的SOLID中的S(SRP, Single responsibility principle)。在函数式当中每一个函数就是一个单元,同样应该只做一件事。但是现实世界总是复杂的,当把现实世界映射到编程时,单一的函数就没有太大的意义。这个时候就需要函数组合和柯里化了。

链式调用

如果用过jQuery的都晓得啥是链式调用,比如$('.post').eq(1).attr('data-test', 'test').javascript原生的一些字符串和数组的方法也能写出链式调用的风格:

1
'Hello, world!'.split('').reverse().join('') // "!dlrow ,olleH"

首先链式调用是基于对象的,上面的一个一个方法split, reverse, join如果脱离的前面的对象”Hello, world!”是玩不起来的。

最近在看Typescript,顺便看了一些函数式编程,然后半个国庆假期就没有了。做个笔记,分几个部分写吧。

最开始接触函数式编程的时候,第一个接触的概念就是高阶函数,和柯里化。咋一看,这不就是长期用来讲作用域的demo吗?我在日常也有用啊,有啥吗?

其实呢,设计模式或则编程范式往往不在于技巧,而在于思想。函数式编程就是一种编程的范式,并不在于技巧多么叼,而在于它的思想。其次才是由设计思想才衍生出来的技巧,技巧往往而言是服务于思想的。所以我觉得最开始学习函数式编程最好先了解一些相关概念和思想会比较好。

函数是一等公民(first class)

如果理解直接看为一等公民的好处好处。

其实说函数式一等公民的意思就是说函数和其他“公民”具有相同的属性。就像任何一种数据类型,它能够被存储在数组中,能够作为函数的参数,能够赋值给变量:

1
2
3
const hello = (name) => (`Hello ${name}!`)
const sayHello = hello; // 作为变量
const helloArray = [hello, sayHello]

上面的代码没有什么意义,只是表达函数在JavaScript中是一等公民,和一个值一样。

函数是一等公民的好处

拿一个callback的例子来讲,比如你用fetch发个请求时:

1
2
3
fetch('getPostLink')
.then(res => renderPosts(res))
.catch(err => handleError(error))

上面其实可以直接传递一个函数作为回调,加一层包裹其实没有必要:

1
fetch('getPostLink').then(renderPosts).catch(handleError)

多一层函数的包裹并没有任何意义,完全是多余的代码。再看一个例子:

1
2
3
4
5
const postController = {
find(postId) { return Db.find(postId) },
delete(postId) { return Db.delete(postId) },
...
}

上面的代码其实就是聚合一些功能作为一个对象,但是多加了一层的函数,也是没有必要的,在阅读的时候到会增加复杂度,其实postController.find === Db.find,所以完全没有再去包裹一层函数:

1
2
3
4
5
const postController = {
find: Db.find,
delete: Db.delete,
...
}

上面的代码是不是更表意,然而如果js的函数不能像值一样传递,上面的简写都是不可能的。上面的代码其实还有一个好处,你不用去纠结如何命名在两层函数之间的参数了。这种风格代码是符合Pointfree的,我们后面要介绍。另外,函数式编程是操作函数的,所以函数是一等公民也是函数式的基石,基本上如果js不支持这一项,函数式根本玩不转。