美文网首页
函子 monad的使用

函子 monad的使用

作者: 笨鸟先飞不 | 来源:发表于2020-12-02 15:35 被阅读0次

    什么是函子 Functor

    是一个特殊容器,通过普通对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理。(包含of静态方法的函子叫Pointed函子,使用也很广泛)

    在函数式编程中的使用

    函数式编程不直接操作值,由函子来完成
    函子就是一个实现来map契约的对象
    我们可以把函子想象成一个盒子,盒子里封装里一个值
    想要处理盒子中的值,我们需要给map方法传递一个处理值的函数(纯函数),由这个函数对值进行处理
    最终map方法返回一个包含新值的盒子(函子)

    普通函子传值异常及解决思路

    class MayBe {
        constructor (value) {
            this._value = value;
        }
        static of (value) {
            return new MayBe(value);
        }
        map (fn) {
            // 注意私有方法及普通对象方法的调用
            return this.isNothing ? MayBe.of(null) : MayBe.of(fn(this._value));
        }
        isNothing () {
            return this._value === null || this._value === undefined;
        }
    }
    
    // 正常传值及调用
    // const r = MayBe.of(null).map(x => x.toUpperCase());
    // console.log(r);
    
    // 异常传值
    // 这里中间会出现null值,虽然结果不会报错,但通过这结果是不知道到底哪一步出错了
    const r = MayBe.of('Hello World').map(x => x.toUpperCase()).map(x => null).map(x => x.split(' '));
    console.log(r);
    
    // 解决办法
    class Left {
        constructor (value) {
            this._value = value;
        }
        static of (value) {
            return new Left(value);
        }
        map (fn) {
            return this;
        }
    }
    class Right {
        constructor (value) {
            this._value = value;
        }
        static of (value) {
            return new Right(value);
        }
        map (fn) {
            return Right.of(fn(this._value));
        }
    }
    
    function parseJSON (str) {
        try {
            return Right.of(JSON.parse(str));
        } catch (e) {
            return Left.of({"error": e.message})
        }
    }
    // const r = parseJSON('{name: zs}');
    // console.log(r);
    
    const r = parseJSON('{"name": "zs"}');
    console.log(r);
    
    
    // 可通过either函数处理异常记录错误信息
    

    函子嵌套及解决思路

    IO 函子:区别与普通函子初始化时传参为函数,而普通函子为普通变量值
    const fp = require('lodash/fp');
    const fs = require('fs');
    
    // IO函子
    class IO {
        constructor (fn) {
            this._value = fn;
        }
        static of (value) {
            return new IO(function () {
                return value
            })
        }
        map (fn) {
            return new IO(fp.flowRight(fn, this._value));
        }
        // monad新增函数
        join () {
            return this._value();
        }
        // monad新增函数
        flatMap (fn) {
            return this.map(fn).join();
        }
    }
    // 读取文件
    function readFile (filename) {
        return new IO(function () {
            return fs.readFileSync(filename, 'utf-8');
        })
    }
    
    // 打印文件中内容
    function print (value) {
        return new IO(function () {
            return value;
        })
    }
    
    // 未使用monad原理时调用
    // const cat = fp.flowRight(print, readFile);
    // // IO(IO(x)) 形成函子嵌套
    // console.log(cat('package.json')._value()._value());
    
    // 优化后使用monad方式调用
    
    // 分析上面调用逻辑
    /**
     * 第一步:调用readFile('package.json')得到一个包含读取文件作为回调函数的IO函子
     * 第二步:调用flatMap传入print,将第一步得到的IO函子和print函数组合,并依次执行,执行第一步得到的IO函子的参数fn即读取文件函数返回读取的文件,以此作为参数再执行print函数,
     * 返回一个IO函子,此时返回的是IO(IO(function(){return value;// 返回读取的文件值})),再执行flatMap内部的join,即调用嵌套IO的_value,即得到嵌套IO内部的IO函数,(因为外层IO的_value就是内层
     * IO)
     * 第三步:再单独执行join函数,即调用上一步返回的内层IO的_value值,即调用print函数中的回调函数,返回获取的文件内容
     * 这样做的好处避免函子嵌套调用,将外部函数转换为纯函数,将不纯部分转到函数内部执行,如上面的第二步,就将IO(IO(fn))函子嵌套放在执行内部了
    */
    
    // const r = readFile('package.json') 
    //             .flatMap(print)
    //             .join();
    // console.log(r);
    
    // 再变更需求
    // 将所有获得的字符串变为大写
    const r = readFile('package.json')
                // .map(x => x.toUpperCase()) //使用数组中的方法
                .map(fp.toUpper)  // 使用lodash/fp模块中的方法
                .flatMap(print)
                .join();
    
    
    
    // 总结
    // 什么是monad,包含一个静态函子和一个join方法叫做monad
    // 什么时候使用monad方法?当一个函数返回一个函子的时候要想到monad方法,主要解决函子嵌套的问题
    // 当我们想要合并函数,函数返回一个值,使用map方法,当函数返回一个函子,使用flatMap方法
    

    Task函子
    folktale库中Task函子的使用

    const { task } = require('folktale/concurrency/task');
    const fs = require("fs");
    const { split, find } = require('lodash/fp');
    
    function readFile (fileName) {
        // 返回一个Task函子,其他细节使用时参照folktale库文档
        return task(resolver => {
            fs.readFile(fileName, 'utf-8', (err, data) => {
                if (err) resolver.reject(err);
                resolver.resolve(data);
            })
        })
    }
    
    const r = readFile('package.json').map(split('\n')).map(find(x => x.includes('version'))).run()
    .listen({
        onRejected: err => {
           console.log(err); 
        },
        onResolved: value => {
            console.log(value);
        }
    });
    // console.log(r);
    

    相关文章

      网友评论

          本文标题:函子 monad的使用

          本文链接:https://www.haomeiwen.com/subject/jhquwktx.html