• js函数式编程


    特点

    1.函数是一等公民
    2.只用表达式不用语句
    3.没有副作用(side effect)
    4.不修改状态
    5.引用透明
    
    

    优势

    1. 代码简洁,开发快速
    2. 接近自然语言,易于理解
    3. 更方便的代码管理
    4. 易于"并发编程"
    5. 代码的热升级
    

    范畴与容器

    我们可以把"范畴"想象成是一个容器,里面包含两样东西。

    class Category{
        constructor(val){
            this.val = val
        }
        addOne(x){
            return x + 1
        }
    }
    
    

    函数的合成与柯里化

    X和Y之间的变形关系是函数f,Y和Z之间的变形关系是函数g,那么X和Z之间的关系,就是g和f的合成函数g·f。

    image

    const compose = (f,g) => {
        return function(x) {
            return f(g(x))
        }
    }
    
    

    满足结合律

    image

    compose(f, compose(g, h))
    // 等同于
    compose(compose(f, g), h)
    // 等同于
    compose(f, g, h)
    

    柯里化

    有了柯里化以后,我们就能做到,所有函数只接受一个参数

    f(x)和g(x)合成为f(g(x)),有一个隐藏的前提,就是f和g都只能接受一个参数。如果可以接受多个参数,比如f(x, y)和g(a, b, c),函数合成就非常麻烦。

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

    函子

    函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。

    左侧的圆圈就是一个函子,表示人名的范畴。外部传入函数f,会转成右边表示早餐的范畴。

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

    image

    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
    }
    
    (new Functor('plus')).map(function(s){
        return s.toUpperCase()
    })
    
    (new Functor('bombs')).map(_.concat(' away')).map(_.prop('length'));
    
    
    上面的例子说明,函数式编程里面的运算,都是通过函子完成,即运算不直接针对值,而是针对这个值的容器----函子。函子本身具有对外接口(map方法),各种函数就是运算符,通过接口接入容器,引发容器里面的值的变形。
    
    因此,学习函数式编程,实际上就是学习函子的各种运算。由于可以把运算方法封装在函子里面,所以又衍生出各种不同类型的函子,有多少种运算,就有多少种函子。函数式编程就变成了运用不同的函子,解决实际问题。
    

    of方法

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

    Functor.of = function(val){
        return new Functor(val)
    }
    
    Functor.of(2).map(function(two){
        return two + 2
    })
    

    Maybe函子

    函子接受各种函数,处理容器内部的值。这里就有一个问题,容器内部的值可能是一个空值(比如null),而外部函数未必有处理空值的机制,如果传入空值,很可能就会出错。

    //报错
    Functor.of(null).map(function(s){
        return s.toUpperCase()
    })
    
    //解决
    class Maybe extends Functor{
        map(f){
            return this.val ? Maybe.of(f(this.val)) : Maybe.of(null)
        }
    }
    Maybe.of(null).map(function(s){
        return s.toUpperCase()
    })
    

    Either函子

    条件运算if...else是最常见的运算之一, 函数式编程里面,使用 Either 函子表达。

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

    class Either extends Functor{
        constructor(left,right){
            this.left = left;
            this.right = right;
        }
        map(f){
            return this.right ? Either.of(this.left,f(this.right)) : 
            Either.of(f(this.left),this.right)
        }
    }
    
    Either.of = function(left,right){
        return new Either(left,right)
    }
    
    var addOne = function(x){
        return x + 1
    }
    
    Either.of(5,6).map(addOne)
    
    Either.of(1,null).map(addOne)
    
    上面代码中,如果右值有值,就使用右值,否则使用左值。通过这种方式,Either 函子表达了条件运算。
    
    
    
    Either 函子的常见用途是提供默认值。下面是一个例子。
    
    Either.of({address:'xxx'},currentUser.address).map(updateField)
    上面代码中,如果用户没有提供地址,Either 函子就会使用左值的默认地址。
    
    
    Either函子的另一个用途是代替try...catch,使用左值表示错误
    
    fucntion 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)
    
    上面代码中,函子A内部的值是2,函子B内部的值是函数addTwo。
    
    有时,我们想让函子B内部的函数,可以使用函子A内部的值进行运算。这时就需要用到 ap 函子。
    
    ap 是 applicative(应用)的缩写。凡是部署了ap方法的函子,就是 ap 函子。
    
    
    class Ap extends Functor {
        ap(F){
            return Ap.of(this.val(F.val))
        }
    }
    
    注意,ap方法的参数不是函数,而是另一个函子。
    
    因此,前面例子可以写成下面的形式。
    
    Ap.of(addTwo).ap(Functor.of(2))
    
    ap 函子的意义在于,对于那些多参数的函数,就可以从多个容器之中取值,实现函子的链式操作。
    function add(x){
        return function(y){
            return x + y
        }
    }
    
    Ap.of(add).ap(Maybe.of(2)).ap(Maybe.of(3))
    
    

    Monad函子

    函子是一个容器,可以包含任何值。函子之中再包含一个函子,也是完全合法的。但是,这样就会出现多层嵌套的函子。

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

    Maybe.of(
        Maybe.of(
            Maybe.of({
                name:'plus',
                number:88888888
            })
        )
    )
    
    class Monad extends Functor{
        join(){
            return this.val
        }
        flatMap(f){
            return this.map(f).join()
        }
    }
    

    IO操作

    I/O 是不纯的操作,普通的函数式编程没法做,这时就需要把 IO 操作写成Monad函子,通过它来完成。

    var fs = require('fs')
    
    var readFile = function(filename){
        return new IO(function(){
            return fs.readFileSync(filename,'utf8')
        })
    }
    
    var print = function(x){
        return new IO(function(){
            console.log(x);
            return x
        })
    }
    
    上面代码中,读取文件和打印本身都是不纯的操作,但是readFile和print却是纯函数,因为它们总是返回 IO 函子。
    
    如果 IO 函子是一个Monad,具有flatMap方法,那么我们就可以像下面这样调用这两个函数。
    
    readFile('xxx').flatMap(print)
    
    这就是神奇的地方,上面的代码完成了不纯的操作,但是因为flatMap返回的还是一个 IO 函子,所以这个表达式是纯的。我们通过一个纯的表达式,完成带有副作用的操作,这就是 Monad 的作用。
    
    由于返回还是 IO 函子,所以可以实现链式操作。因此,在大多数库里面,flatMap方法被改名成chain。
    
    var tail = function(x){
        return new IO(function(){
            return x[x.length-1]
        })
    }
    
    readFile('xxx').flatMap(tail).flatMap(print)
    //等同于
    readFile('xxx').chain(tail).chain(print)
    

    实战

    class Functor {
      constructor(val){
         this.val = val
      }
      map(f){
        return new Functor(f(this.val))
      }
      static of(val){
        return new Functor(val)
      }
    }
    
    const functor1 = Functor.of(2).map(two => two + 2)
    const functor2 = Functor.of('plus').map(name => name.toUpperCase())
    
    //console.log(functor2.val)
    
    
    //const err = Functor.of(null).map(err => err.toUpperCase())
    class Maybe extends Functor {
      map(f){
        return this.val ? Maybe.of(f(this.val)) : Maybe.of(this.val)
      }
      static of(val){
        return new Maybe(val)
      }
    }
    
    // const err = Maybe.of(null).map(err => err.toUpperCase())
    
    
    class Either extends Functor {
      constructor(left,right){
        super()
        this.left = left
        this.right = right
      }
      map(f){
        return this.right ? Either.of(this.left,f(this.right)) : Either.of(f(this.left),this.right)
      }
    }
        
    Either.of = function(left,right){
        return new Either(left,right)
        
    }
    
    const addOne = x => x + 1
    
    const res = Either.of(5,6).map(addOne)
    const res2 = Either.of(1,null).map(addOne)
    
    
    class Ap extends Functor{
      ap(F){
        return Ap.of(this.val(F.val))
      }
      static of(val){
        return new Ap(val)
      }
    }
    const addTwo = x => x + 2
    const add = x => y => x + y
                     
    const res3 = Ap.of(addTwo).ap(Functor.of(2))
    const res6 = Ap.of(add).ap(Maybe.of(2)).ap(Maybe.of(3))
     console.log(res6)
                     
                     
    
    
  • 相关阅读:
    mysql DATE_FORMAT FROM_UNIXTIME 的区别
    thinkphp按日期(天)统计数据
    ThinkPHP5 (mySQL) 统计各个时间段内的订单量
    转:mysql按时间统计数据,没有数据的时候自动补充0
    SQL的各种连接Join详解
    查询近7天,近1个月,近3个月每天的数据量,查询近一年每个月的数据量
    MySQL 如何生成日期表
    MySQL 生成日期表
    linux达人养成计划学习笔记(一)——命令基本格式及文件处理命令
    rviz学习笔记(二)——Markers: Points and Lines (C++) 点和线
  • 原文地址:https://www.cnblogs.com/linybo/p/13322427.html
Copyright © 2020-2023  润新知