函数式编程-函子

参考:
JS函数式编程03--函子

函子的概念

函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。
函子首先是一个容器,它包含了值和值的变形关系,这个变形关系就是函数。
函子可以把函数式编程副作用控制在可控的范围内,包括处理异常,异步操作等。
一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。

函子容器

函数式编程约定:

  • 函子有一个of方法,用来生成新的容器
  • 容器map方法将容器内每一个值映射到另外一个容器
    function Container(value){
        this.value = value;
    }

    // 函数式编程一般约定,函子有一个of方法
    Container.of = function(value){
        return new Container(value)
    };

    // 函数式编程一般约定,容器具有map方法,该方法将容器内每一个值映射到另外一个容器
    Container.prototype.map = function(fn){
        return Container.of(fn(this.value))
    }

    function log(value){
        console.log(value);
    }
    Container.of("hello").map(log); // hello

    function add(value){
        return value+1
    }
    let {value}=Container.of(1).map(add).map(add);
    console.log(value) // 3

es6 实现:

class Container {
    static of(val){
        return new Container(val)
    }
    constructor(val){ 
        this.val = val;
    }
    map(fn){
        return Container.of(fn(this.val))
    }
}

let fn = Container.of(2).map(val=>val+2)

console.log(fn.val) // 4

Maybe 函子

函子会接收各种函数来处理内部的值,这里就有可能遇到错误,我们需要对这些错误做处理,MayBe函子的作用就是对外部的空值情况做处理。

    function Maybe(x){
        this.__value = x;
    }
    Maybe.of = function(x){
        return new Maybe(x);
    }
    // 在map中设置空值检查 
    Maybe.prototype.map = function(fn){
        return this.isNothing() ? Maybe.of(null) : Maybe.of(fn(this.__value))
    }
    // 函子接收各种函数,处理容器内部的值,如果是空值会引发错误,这里引入错误判断机制
    Maybe.prototype.isNothing = function(){
        return (this.__value === null || this.__value === undefined)
    };

    function log(val){
        console.log(val.toString())
    }

    Maybe.of(null).map(log) // 不执行
    Maybe.of("hello").map(log) // 执行

错误处理 Either

函数式编程中,try/catch/throw等异常捕获并不是纯函数,通过Either模拟错误处理。

Either函子与if...else处理很相似。它内部有两个值,左值和右值。右值通常代表正常的值,左值是当右值不存在或错误时的默认值

class Either {
    static of(left, right) {
      return new Either(left, right);
    }
    constructor(left, right) {
      this.left = left;
      this.right = right;
    }
    map(fn) {
      return this.right ? 
                Either.of(this.left, fn.call(this,this.right)) : 
                Either.of(fn.call(this,this.left), this.right)
    }
  }

  Either.of("left").map((value)=>console.log(value)); //left
  Either.of(null, "right").map((value)=>console.log(value)); // right

  Either.of("error","success")
        .map(function(value){
            console.log(this)
            console.log(value)
            return value+" next"
        })
        .map(value=>{
            console.log(value)
         })

  // 模拟请求
  const request = Math.random() > .5 ? "success" : false;
  // error
  const error = "异常处理";
  // 响应结果
  const result = (value)=>console.log("get result: %s",value);

  Either.of(error, request).map(result);
const Left = function(value){
    this.value = value;
}
const Right = function(value){
    this.value = value;
}
Left.of = function(value){
    return new Left(value) 
}
Right.of = function(value){
    return new Right(value) 
}

// left 直接抛出容器,将错误信息传递下去
Left.prototype.map = function(fn){
    return this;
}
Left.prototype.catch = function(fn){
    fn && fn.call(this,this.value)
}
Right.prototype.map = function(fn){
    return Right.of(fn(this.value));
}

const getAge = user=> (user.age ? Right.of(user.age) : Left.of("ERROR"));

getAge({name:"chen",age:12}).map((age)=>console.log(`age is ${age}`));
getAge({name:"chen"}).map((age)=>console.log(`age is ${age}`)).catch(error=>console.log(error));

AP 函子

函子中的值有可能是数值,也有可能是一个函数,我们想让值为函数的函子用另一个函子中的值运算,我们就可以用ap函子

class Ap {
    static of(value){
        return new Ap(value);
    }
    constructor(value){
        this.value = value
    }
    ap(value){
        return Ap.of(this.value(value))
    }
}

function add(num){
    return num+1
}

let {value} = Ap.of(add).ap(2)
console.log(value)

综合 compose

const fs = require("fs");
const _ = require("lodash");

class Functor {
	constructor(value){
		this.value = value
	}
};

class Monad extends Functor {
	flatMap(newFn){
		let io = this.map(newFn);
		// value 经过变成新旧方法组合在一起的 compose ,
		return io.value;
	}
}

/* _.flowRight 使用
*
	const square = ()=>(n*n);
	const addSquare = _.flowRight([square, _.add]);
	addSquare(1, 2); // 9
*
*/
const compose=_.flowRight;

class IO extends Monad {
	static of(fn){
		return new IO(fn)
	}
	map(fn){
		// 组合新旧方法  _.flowRight(newFn,oldFn)
		let compFn = compose(fn,this.value);
		// 返回实例,组合后的方法赋值给属性value this.value = compFn
		return IO.of(compFn)
	}
}

const readFile = (filename)=>{
	return IO.of(()=>fs.readFileSync(filename,"utf-8"))
}
const printIt = (value)=>{
	return IO.of(()=>`print: ${value}`)
}
const tail = (value)=>{
	return IO.of(()=>console.log(value))
}

// 返回组合函数
let composeFn = readFile("./test.txt").flatMap(printIt)

// io instance
let io = composeFn();

// 继续组合
io = io.flatMap(tail)();

// 执行最终结果
io.value();

// readFile("./test.txt").flatMap(printIt)().flatMap(tail)();