函子
函子的概念
函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。
函子首先是一个容器,它包含了值和值的变形关系,这个变形关系就是函数。
函子可以把函数式编程副作用控制在可控的范围内,包括处理异常,异步操作等。
一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。
函子的基本构造
函子就是一个特殊的容器,它可以由对象来实现,这个对象中包含了值,这个值永远不会对外公布,有一个map方法,用来操作这个值。还有一个of方法,用来生成一个新的容器。
class Functor {
static of(value){
return new Functor(value)
}
constructor(val){
this._value = val
}
map(fn){
return Functor.of(fn(this._value ))
}
}
//使用的时候
const func = Functor.of.map(x => x+1)
func(1)//Container { _value: 2 }
这里总结一下函子的使用
- 程序运算不会直接操作值,而是通过函子来完成
- 由map处理后返回的是一个新的对象,我们可以继续链式的操作值
- 我们可以把函子想象成一个盒子,盒子中封装着一个值,当我恩处理盒子中的值的时候我们要用到盒子专门改变值的工具:map,我们需要给盒子的map方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理,最终map方法返回一个包含新值的盒子(函子)。
MayBe函子
函子会接收各种函数来处理内部的值,这里就有可能遇到错误,我们需要对这些错误做处理,MayBe函子的作用就是对外部的空值情况做处理。
MayBe函子的构造就是在map中设置空值检查
class Maybe{
static of(value){
return new Maybe(value)
}
constructor(val){
this._value = val
}
map(f) {
return this.val ? Maybe.of(f(this.val)) : Maybe.of(null);
}
}
虽然 MayBe函子可以避免出现错误,但是多次调用map时我们并不知道哪里出现了错误
Either函子
Either函子与if...else处理很相似。它内部有两个值,左值和右值。右值通常代表正常的值,左值是当右值不存在或错误时的默认值
class Either {
static of(left,right){
return new Either (left,right))
}
constructor(left,right){
this.left = left
this.right = right
}
map(fn){
return this.right ? Either.of(this.left,fn(this.right)) : Either.of(fn(this.left),right)
}
}
此外,Either函子另一个用途是替代try...catch,使用左值来表示错误
function parseJSON(json) {
try {
return Either.of(null, JSON.parse(json));
} catch (e: Error) {
return Either.of(e, null);
}
}
ap函子
函子中的值有可能是数值,也有可能是一个函数,我们想让值为函数的函子用另一个函子中的值运算,我们就可以用ap函子
function add(x) {
return x + 1
}
const A = Functor.of(2)
const B = Functor.of(add)
class Ap extends Functor {
ap(F) {
return Ap.of(this.val(F.val))
}
}
//我们想让B函子的值使用A函子的值
Ap.of(add).ap(Functor.of(2))
凡是部署了ap方法的函子,就是ap函子。ap函子的意义在于对多参数的函数,可以从多个容器中取值,实现函子的链式调用。
Monad 函子
函子中的值可以接受任何值,所以函子之中可以包含另一个函子。这样就会造成函子多层嵌套的问题。取值时会很不方便。Monad函子的作用就是:总是返回一个单层的函子,它有一个FlatMap方法,与map方法作用相同,唯一的区别就是如果生成了一个嵌套函子,它会取出后者的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。
class Monad extends Functor {
join() {
return this.val;
}
flatMap(f) {//f是一个函子
return this.map(f).join();
}
}
如果函数f返回的是一个函子,那么this.map(f)就会生成一个嵌套的函子。所以,join方法保证了flatMap方法总是返回一个单层的函子。这意味着嵌套的函子会被铺平。
IO函子
I/O是一个不纯的操作,普通的函数式编程无法处理,所以使用IO函子操作
const fp = require('lodash/fp')
class IO {
static of (value) {
return new IO (function () {
return value
})
}
constructor(fn) {
this._value = fn
}
map (fn){
return new IO (fp.flowRight(fn,this._value));
}
}
- IO函子中的_value是一个一个函数,这里是把函数作为值来处理
- IO函子可以把不纯的动作存储到_value中,延迟这个不纯的操作(惰性执行),包装当前的操作是纯的操作
- 把不纯的操作交给调用者来处理。
参考
网友评论