美文网首页
函数式编程

函数式编程

作者: 定格r | 来源:发表于2018-06-30 14:36 被阅读0次
    总之,在函数式编程中,函数就是一个管道(pipe)。这头进去一个值,那头就会出来一个新的值,没有其他作用。

    一、函数的合成与柯里化

    1.1函数的合成

    如果一个函数要经过多个函数才可以变成另一个值,这时候就要把函数的中间步骤合并成一个函数,这就叫做函数的合成。

    例如:


    图片.png
    const compose = function (f, g) {
      return function (x) {
        return f(g(x));
      };
    }
    
    1.2.柯里化

    把一个多参数的函数,转化为单参数的函数

    // 柯里化之前
    function add(x, y) {
      return x + y;
    }
    
    add(1, 2) // 3
    
    // 柯里化之后
    function addX(y) {
      return function (x) {
        return x + y;
      };
    }
    
    addX(2)(1) // 3
    

    二 ,函子

    函数不仅可以用于同一个范畴之中值的转换,还可以用于将一个范畴转成另一个范畴。这就涉及到了函子(Functor)。

    2.1函子的概念

    函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。
    它的变形关系可以依次作用于每一个值,将当前容器变形成另一个容器。

    2.1函子的代码实现

    任何具有map方法的数据结构,都可以当做函子的实现

    class Functor {
      constructor(val) { 
        this.val = val; 
      }
    
      map(f) {
        return new Functor(f(this.val));
      }
    }
    

    上面代码中,Functor是一个函子,它的map方法接受函数f作为参数,然后返回一个新的函子,里面包含的值是被f处理过的(f(this.val))。

    一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。

    
    (new Functor(2)).map(function (two) {
      return two + 2;
    });
    // Functor(4)
    
    (new Functor('flamethrowers')).map(function(s) {
      return s.toUpperCase();
    });
    // Functor('FLAMETHROWERS')
    
    (new Functor('bombs')).map(_.concat(' away')).map(_.prop('length'));
    // Functor(10)
    

    三 、of 方法

    函数式编程一般约定,函子有一个of方法,用来生成新的容器。

    下面用 of 替换掉 new

    Functor.of = function(val) {
      return new Functor(val);
    };
    

    这个例子可以改成

    
    Functor.of(2).map(function (two) {
      return two + 2;
    });
    // Functor(4)
    

    四、Maybe 函子

    Maybe 函子的map方法里面设置了空值检查。 防止出现空值而报错

    
    Functor.of(null).map(function (s) {
      return s.toUpperCase();
    });
    // TypeError
    

    像这个函数就因为有空值所以就报错了

    然而 Maybe 函子 就是这样检查的:

    class Maybe extends Functor {
      map(f) {
        return this.val ? Maybe.of(f(this.val)) : Maybe.of(null);
      }
    }
    

    有了 Maybe 函子,处理空值就不会出错了。

    Maybe.of(null).map(function (s) {
      return s.toUpperCase();
    });
    // Maybe(null)
    

    五、Either 函子

    1.Either 函子相当于 if,,,else,,,语句。

    Either 函子内部有两个值:左值(Left)和右值(Right)。右值是正常情况下使用的值,左值是右值不存在时使用的默认值。

    var addOne = function (x) {
      return x + 1;
    };
    
    Either.of(5, 6).map(addOne);
    // Either(5, 7);
    
    Either.of(1, null).map(addOne);
    // Either(2, null);
    

    上面代码中,如果右值有值,就使用右值,否则使用左值。通过这种方式,Either 函子表达了条件运算。

    2.Either 函子的另一个用途是代替try...catch,使用左值表示错误。

    
        function parseJSON(json) {
          try {
            return Either.of(null, JSON.parse(json));
          } catch (e: Error) {
            return Either.of(e, null);
          }
        }
    

    上面代码中,左值为空,就表示没有出错,否则左值会包含一个错误对象e。一般来说,所有可能出错的运算,都可以返回一个 Either 函子。

    五、ap 函子

    function addTwo(x) {
      return x + 2;
    }
    
    const A = Functor.of(2);
    const B = Functor.of(addTwo)
    

    ap 函子就是可以让函子 B 内部的函数,可以使用函子 A 内部的值进行运算。

    
    class Ap extends Functor {
      ap(F) {
        return Ap.of(this.val(F.val));
      }
    }
    

    注意,ap方法的参数不是函数,而是另一个函子。

    因此,上面的例子可以这样写

    Ap.of(addTwo).ap(Functor.of(2))
    // Ap(4)
    

    ap 函子的意义在于,对于那些多参数的函数,就可以从多个容器之中取值,实现函子的链式操作。

    function add(x) {
      return function (y) {
        return x + y;
      };
    }
    
    Ap.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));
    //或者这样写
    Ap.of(add(2)).ap(Maybe.of(3));
    // Ap(5)
    

    六、Monad 函子

    函子是一个容器,可以包含任何值。这样就出现了多层嵌套的函子。

    Maybe.of(
      Maybe.of(
        Maybe.of({name: 'Mulburry', number: 8402})
      )
    )
    

    上面这个函子,一共有三个Maybe嵌套。如果要取出内部的值,就要连续取三次this.val。这当然很不方便,因此就出现了 Monad 函子。

    Monad 函子的作用是,总是返回一个单层的函子。它有一个flatMap方法,与map方法作用相同,唯一的区别是如果生成了一个嵌套函子,它会取出后者内部的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。

    class Monad extends Functor {
      join() {
        return this.val;
      }
      flatMap(f) {
        return this.map(f).join();
      }
    }
    

    上面代码中,如果函数f返回的是一个函子,那么this.map(f)就会生成一个嵌套的函子。所以,join方法保证了flatMap方法总是返回一个单层的函子。这意味着嵌套的函子会被铺平(flatten)。

    七、IO 操作

    Monad 函子的重要应用,就是实现 I/O (输入输出)操作。

    
        var fs = require('fs');
    
        var readFile = function(filename) {
          return new IO(function() {
            return fs.readFileSync(filename, 'utf-8');
          });
        };
    
        var print = function(x) {
          return new IO(function() {
            console.log(x);
            return x;
          });
        }
    

    上面代码中,读取文件和打印本身都是不纯的操作,但是readFile和print却是纯函数,因为它们总是返回 IO 函子。

    如果 IO 函子是一个Monad,具有flatMap方法,那么我们就可以像下面这样调用这两个函数。

    readFile('./user.txt')
    .flatMap(print)
    

    由于返回还是 IO 函子,所以可以实现链式操作。因此,在大多数库里面,flatMap方法被改名成chain。

    
    var tail = function(x) {
      return new IO(function() {
        return x[x.length - 1];
      });
    }
    
    readFile('./user.txt')
    .flatMap(tail)
    .flatMap(print)
    
    // 等同于
    readFile('./user.txt')
    .chain(tail)
    .chain(print)
    

    相关文章

      网友评论

          本文标题:函数式编程

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