我们已经对函子有一个简单的认识,我们可以把函子想象成一个盒子,盒子里保存一个值,通过调用盒子的map方法可以传入一个函数,通过这个函数对盒子里面的值进行处理。

接下来我们来学习一下IO函子,也就是输入输出的函子,他和之前函子不同的地方在于,他内部的value始终是一个函数。

IO函子就是把不纯的操作都存储在value中,value中存储的是函数,在函子内部并没有调用这个函数,通过IO函子是延迟执行了这些不纯的操作,也就相当于惰性执行。

通过IO函子先包装一些函数,当我们需要的时候,再来执行这些函数,因为IO函子中存储的函数有可能是不纯的,但是通过IO函子包装起来的话,我们当前的操作就是一个纯的操作。把不纯的操作延迟到调用的时候。

有了IO函子就可以把各种不纯的操作装进笼子里,但是这些不纯的操作,最终都要执行的,我们可以把这些不纯的操作交给调用者来处理。

使用IO函子的时候,先创建一个IO的类,构造函数接收一个函数,这和之间是不一样的,我们这里把这个函数存起来。在of方法中也和之前不一样,of方法接收的是一个数据,在of方法里面返回一个IO函子,在构造函数中传入一个函数,这个函数中返回数据。

通过of方法我们可以看出,IO函子最终还是想要返回的数据,只不过这里通过一个函数把这个值包裹起来了,IO函子的value保存的是这个函数。这个函数返回的是一个值,他把求值的过程做了延迟处理,当我们想要这个值的时候再调用IO函子的value函数。

这里的map方法和之前也是不同的,map韩式接收一个fn函数,在map方法里面通过调用IO的构造函数来创建一个IO的函子,参数里面调用了fp的flowRight将fn和value组合起来,最终得到新的函数传递给IO的构造函数,得到一个IO函子,并且返回。

const  fp = require('lodash/fp');

class IO {
    static of (x) {
        return new IO(function() {
            return x;
        })
    }
    consturctor (fn) {
        this._value = fn;
    }
    map (fn) {
        return new IO(fp.flowRight(fn, this._value));
    }
}

我们首先调用IO的of方法返回一个函子,of方法接收一个值,我们可以传入一个process(Node环境才可以的)。

接下来我们调用map方法,来获取process中的exePath,就是当前node环境中进程的执行路径。

let r = IO.of(process).map(p => p.execPath);

console.log(r);

可以发现我们这里返回的是一个IO函子,这个IO函子中的value保存的是函数function,我们来分析一下这个函数是谁。

当我们调用IO.of的时候我们传入了process对象,在of我们返回一个函子,并且把process包装到函数中,接着调用map方法,在map方法中调用flowRight把of中包裹process的函数组合上map传入的函数。返回一个IO函子。

这个function就是当前函子的value也就是组合之后的函数。

那接下来我们想要获取这个执行结果,想要调用IO函子中的函数,我们看到IO中的value就是一个函数,所以我们可以r._value()直接调用。

let r = IO.of(process).map(p => p.execPath);

console.log(r._value());

这里总结一下,IO函子内部包装了一些函数,我们在传递函数的时候有可能这个函数是一个不纯的操作,我们不关心这个函数是否纯净,IO函子在执行的过程中返回的结果始终是一个纯的操作。

IO中有可能包裹了一些不纯的操作,但是当前的执行始终是一个纯的操作,调用map方法的时候始终会返回一个IO函子,但是IO函子的value属性里面保存的一些函数,因为他里面最终要去合并很多函数,所以他可能是不纯的。我们将不纯的操作延迟到了调用的时候,也就是通过IO函子控制了副作用在可控的范围内发生。