函数式编程-函子
参考:
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)();