美文网首页
函数式编程

函数式编程

作者: thdqn | 来源:发表于2019-07-25 16:04 被阅读0次

    我觉得学习函数式编程以案例来学习是最快的方法,我看了很多讲解还是实操起来学的快,推荐函数式编程指南这本书,同时还要看ramda,我是一开始用的ramda这个工具,这个工具让函数式写起来更方便。因为这本书中运用到了ramda,所有我觉得有必要两个同时看,看完书前几张基本概念的理解后,等看到到案例了再去看ramda,这样学起来就快了。如果网上找不到书的可以问我要。
    demo大都是以react为案例来写的

    一些概念

    容器

    我的理解:有了这个容器,每次执行函数一次就把值又放到容器里面了,再次操作再次取值再次放入进去,十分方便,

    // Container就是这个容器
    var Container = function(x) {
      this.__value = x;
    }
    
    Container.of = function(x) { return new Container(x); };
    
    // 加了map后就是另外一种形式了叫做functor,
    // functor 是实现了 map 函数并遵守一些特定规则的容器类型
    
    // map传入函数,执行完用of再次把值放入容器里面,
    Container.prototype.map = function(f){
      return Container.of(f(this.__value))
    }
    
    // 这里就展示了两次在map里面执行返回数值到容器里面
    Container.of("bos").map(concat("waw")).map(R.prop("length"));
    // 结果 {__value: 6}
    

    这里如果看不懂可以这样理解

    // 这是一次map
    Container.of("bos").map(concat("waw"))
    // 这里执行map后 相当于变成了
    var a = concat("waw")("bos");
    Container.of(a);
    
    // 这是两次map
    Container.of("bos").map(concat("waw")).map(R.prop("length"));
    // 这里变成了
    var a = concat("waw")("bos");  // 合并的字符串
    // R是ramda语法
    var len = R.prop("length")("wawbos")  // wawbos的length 是=>6;
    Container.of(len);
    // {__value: 6}
    

    Maybe

    这个是在容器里面添加了空值判断,Maybe 会先检查自己的值是否为空,然后才调用传进来的函数。这样我们在使用 map 的时候就能避免的空值了的麻烦了

    var Maybe = function(x){
      this.__value = x;
    }
    Maybe.of = (x) =>{
      return new Maybe(x);
    }
    
    Maybe.prototype.isNothing = function() {
      return (this.__value === null || this.__value === undefined);
    }
    
    Maybe.prototype.map = function(f){
      return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value))
    }
    Maybe.of("absck kbas").map(match(/a/ig)); // => {"__value": ["a", "a"]}
    Maybe.of(null).map(match(/a/ig)); // => {"__value": null}
    Maybe.of({name: "brois"}).map(R.prop("age")).map(add(10)); // => {"__value": null}
    

    纯函数

    纯函数是一种相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用

    var xs = [1,2,3,4,5];
    
    // 纯的
    xs.slice(0,3);
    //=> [1,2,3]
    
    xs.slice(0,3);
    //=> [1,2,3]
    
    xs.slice(0,3);
    //=> [1,2,3]
    
    // 不纯的
    xs.splice(0,3);
    //=> [1,2,3]
    
    xs.splice(0,3);
    //=> [4,5]
    
    xs.splice(0,3);
    //=> []
    
    // ----------------------
    
    // 不纯的
    var minimum = 21;
    
    var checkAge = function(age) {
      return age >= minimum;
    };
    
    
    // 纯的
    var checkAge = function(age) {
      var minimum = 21;
      return age >= minimum;
    };
    
    // 这种可以理解为不改变外部变量,或者不受外部变量而影响,
    
    // ---------------------
    
    // 不纯的
    var signUp = function(attrs) {
      var user = saveUser(attrs);
      welcomeUser(user);
    };
    
    var saveUser = function(attrs) {
        var user = Db.save(attrs);
        ...
    };
    
    var welcomeUser = function(user) {
        Email(user, ...);
        ...
    };
    
    // 纯的
    var signUp = function(Db, Email, attrs) {
      return function() {
        var user = saveUser(Db, attrs);
        welcomeUser(Email, user);
      };
    };
    
    var saveUser = function(Db, attrs) {
        ...
    };
    
    var welcomeUser = function(Email, user) {
        ...
    };
    
    

    副作用包含

    • 更改文件系统
    • 往数据库插入记录
    • 发送一个 http 请求
    • 可变数据
    • 打印/log
    • 获取用户输入
    • DOM 查询
    • 访问系统状态

    IO

    像这样的虽然是IO操作,但是是纯函数的方式,不会受外部的影响,

    var getFromStorage = function(key) {
      return function() {
        return localStorage[key];
      }
    }
    

    一般不会用上面这种,而是像下面这样,

    var IO = function(f) {
      this.__value = f;
    }
    
    IO.of = function(x) {
      return new IO(function() {
        return x;
      });
    }
    
    IO.prototype.map = function(f) {
      return new IO(R.compose(f, this.__value));
    }
    

    IO 跟之前的 functor 不同的地方在于,它的 __value 总是一个函数
    IO 把非纯执行动作包裹到函数里,目的是延迟执行这个非纯动作。

    实际操作

    var url = new IO(function () {return window.location.href;});
    
    var toPairs = R.compose(R.map(R.split('=')), R.split('&'));
    var fii = function(key) {
        return R.map(R.compose(R.concat(key), R.split('/')), url);
    }
    fii("地址:").__value(); // => 地址: path,xxxx
    

    monad

    一个 functor,只要它定义个了一个 join 方法和一个 of 方法,并遵守一些定律,那么它就是一个 monad
    这个的场景比如当用到多层io的时候会出现map(map(f));这个时候其实无需嵌套一层的,直接返回里面的值就可以了,

    var maybe = function(x){
            this._value = x;
          }
          maybe.of = function(x) {
            return new maybe(x)
          }
          maybe.prototype.map=function(f){
            return maybe.of(f(this._value));
          };
          maybe.prototype.isNothing = function(){
            return (this._value === null || this._value === undefined)
          }
          // 注意这里的join,返回了_value
          maybe.prototype.join = function(){
            return this.isNothing() ? Maybe.of(null) : this._value;
          }
          var safeProp = R.curry(function(x, obj) {
            return new maybe(obj[x]);
          });
    
          var safeHead = safeProp(0);
    
          var joinTest = function(mma){
            return mma.join()
          }
    
          var address = R.compose(
          joinTest,
          R.map(safeProp('street')),
          joinTest,
          R.map(safeHead),
          safeProp("address"))
          let arrs =  {
               address:
                 [
                   {
                     street: {name: 'Mulburry', number: 8402},
                     postcode: "WC2N"
                   }
                 ]
                };
          let a = address(arrs);
          // a => maybe{_value: {name: "Mulburry", number: 8402}}
    

    了解了join,然后把map和join打包在一个函数里面,这个函数可以叫做chain或者flatMap,他的形式如下

    var chain = curry(function(f, m){
      return m.map(f).join(); // 或者 R.compose(join, map(f))(m)
    });
    

    然后可以把上面的代码优化下

    var address = R.compose(
        chain(safeProp('street')),
        chain(safeHead),
        safeProp("address"))
    // 为什么chain里面直接用参数自带的map而上面的是R.map,因为这里的参数是maybe,他的map方法和Ramda的原理是一样的,
    

    applicative functor

    applicative functor 是实现了 ap 方法的 pointed functor
    场景:把一个 functor 的函数值应用到另一个 functor 的值上
    比如:

    R.add(Container.of(2), Container.of(3));
    

    这种是不对的,add取不到里面的值,需要一个ap函数来把一个 functor 的函数值应用到另一个 functor 的值上

    Container.prototype.ap=function(f){
        return f.map(this.__value)
    }
    
    Container.of(R.add(2)).ap(Container.of(3));
    

    这里比较绕,做好心理准备,从左向右执行,Container.of(R.add(2)),这里构造了一个Container,里面的__value是R.add(2)这个函数,然后它调用了ap方法,这个参数是Container.of(3),所以

    Container.prototype.ap=function(f){

    这里的 fContainer.of(3),然后 Container.of(3)调用了map方法,传的参数是this.__value,注意这里重点,这个 this.__valueR.add(2) ; 因为是Container.of(R.add(2))调用的ap方法,

    return f.map(this.__value)
    }

    然后 f.map(this.__value)这里调用的事

    Container.prototype.map = function (f) {

    这里的f就是 R.add(2)了,因为是Container.of(3)调用的map方法,所以这里的this.__value就是3了, 最后就成了 R.add(2)(3);

    return Container.of(f(this.__value))
    }

    如果还是没理解,在我注释的地方console一下就明白了,

    上面的方法还可以写成
    Container.of(2).map(R.add).ap(Container.of(3));

    案例

    demo1 遍历数组返回虚拟dom

        // 定义好数据
        constructor(props){
            super(props)
            this.state = {
                data = [{
                    id: 1,
                    name: "王"
                }, {
                    id: 2,
                    name: "李"
                }, {
                    id: 3,
                    name: "张"
                }]
            }
        }
        // 由map 遍历数据,再由addIndex给每个数据加上一个index,并返回dom,就是我们需要的虚拟dom了
        domap = ()=> R.addIndex(R.map(this.doRender, this.state.data))
        doRender = (data,key) => {<p key={key}>{data.name}</p>}
        render() {
            return {this.domap()}
        }
    

    demo2 fetch封装

    
    

    阮一峰ramda介绍
    ramda中文网
    ramda英文网

    个人博客地址

    相关文章

      网友评论

          本文标题:函数式编程

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