ES6函数式编程之-函子、管道、组合(Compose、Pipe)

本文是对《ES6函数式编程》的学习记录

文章目录

      • compose函数
      • 管道/序列
      • 组合的优势
      • 函子
        • 函子实现map
      • MayBe函子
        • MayBe真实用例
      • Either函子
        • 实现Either函子
        • reddit例子的Either版本

compose函数

组合的思想就是把小函数组合成大函数

  • 组合单个函数
const compose = (a,b)=>(c)=>a(b(c));

let splitIntoSpaces = (str)=>str.split(" ");
let count = (array)=>array.length;
//构建新函数计算字符串中单词的数量
const countWords = compose(count,splitIntoSpace);
countWords("Hello your reading about composition"); //5
  • 组合多个函数
const compose = (...fns)=>
(value)=>reduce(fns.reduce(),(acc,fn)=>fn(acc),value);

//判断一个字符串长度是奇数还是偶数
let oddOrEven = (ip)=>ip%2==0?"even":"odd";
const oddOrEvenWords = compose(oddOrEven,count,splitIntoSpaces);
oddOrEvenWords("Hello  your reading about composition");//odd

管道/序列

compose的数据流是从右到左,因为最右侧的函数首先执行,将数据传递给下一个函数,最左侧的最后执行。pipe函数就是从左到右处理数据流的过程称为管道(pipeline)或者序列(sequence)

const pipe = (...fns)=>
(value)=>reduce(fns,(acc,fn)=>fn(acc),value);//fns没有使用reverse

//重新执行
const oddOrEvenWords = pipe(splitIntoSpace,count,oddOrEven);
oddOrEvenWords("Hello  your reading about composition");//odd

组合的优势

  • 组合满足结合律
compose(f,compose(g,h)) == compose(compose(f,g),h);
//compose(compose(f,g),h)
let oddOrEvenWords = compose(compose(oddOrEven,count),splitIntoSpaces);
let oddOrEvenWords("Hello  your reading about composition");//odd

//compose(f,compose(g,h))
let oddOrEvenWords = compose(oddOrEven,compose(count,splitIntoSpaces));
let oddOrEvenWords("Hello  your reading about composition");//odd

函子

用一种纯函数式的方式帮助我们处理错误
::: tip

  • 函子是什么:函子是一个普通对象(在其他语言中,可能是一个类),他实现了map函数,在遍历每个对象值的时候生成一个新对象。
    :::
const Container = function(val){
    this.val = val;
}

//使用箭头函数 由于this在箭头函数中没有 prototype和constructor 因此会报错
const Container = function(val)=>{
    this.val = val;
}
//当new container的时候,将会报错如下: Container is not a constructor(...)(anonymous function)

//应用container
let testValue = new Container(3)     //Container(value:3)
let testObj = new Container({a:1})   //Container(value:{a:1})
let testArray = new Container([1,2]) //Container(value:[1,2])

//of方法定义
Container.of = function(value){
    return new Container(value);
}

//用of创建container
testValue = Container.of(3) //Container(value:3)
testObj = Container.of({a:1})  //Container(value:{a:1})
testArray = Container.of([1,2]) //Container(value:[1,2])

//container.of嵌套 将输出如下
Container{
    value:Container{
        value:3,
    }
}

函子实现map

Container.prototype.map =function(fn){
    return Container.of(fn(this.value));
}

let double =(x)=>x+x;
Container.of(3).map(double) //Container(value:6)

//container链式调用
Container.of(3).map(double).map(double).map(double) //Container{value:24}

::: tip
函子是一个普通对象(在其他语言中,可能是一个类)它实现了map函数,在遍历每个对象值的时候生成了一个新对象-函子是实现了map契约的对象
:::

MayBe函子

处理函数中的代码

//MayBe定义
const MayBe = function(val){
    this.val = val;
} 
MayBe.of = function(val){
    return new MayBe(val);
}
//MayBe的map函数定义
MayBe.prototype.isNothing = function(){
    //应用传入函数之前先检查容器中的值是否为null或者undefined
    return (this.value === null||this.value===undefined);
};
MayBe.prototype.map = function(fn){
    //map把应用函数的返回值放回了容器
    return this.isNothing()?MayBe.of(null):MayBe.of(fn(this.value));
};

// in action
//创建一个MayBe
MayBe.of("String")map((x)=x.toUpperCase())
//返回
MayBe {vale:"STRING"}
//更重要和有趣的是 x是否是null或者undefined并不关心。 它已经被MayBe函子抽象出来了
(x)=>x.toUpperCase()
//传入null
MayBe.of(null).map((x)=>toUpperCase())
//it returns 
MayBe {value:null}

//map链式调用
MayBe.of("George")
   .map((x)=>x.toUpperCase())
   .map((x)=>"Mr. "+ x)
//MayBe {value: "Mr. GEORGE "}

MayBe真实用例

用一个api获取Reddit网站子版块的Top10数据

let getTopTenSubRedditPosts = (type)=>{
    let response 
    try{
        response = JSON.parse(request('GET','https://www.reddit.com/r/subreddits/'+type+".json?limit=10").getBody('utf8'))
    }catch(err){
        response = {message:"Something went wrong",errorCode: err['statusCode']}
    }
    return response;
}
//request 来自 sync-request
//调用api
getTopTenSubRedditPosts("new")

使用maybe实现获取Reddit子版块的Top10帖子

//导入类库的ArrayList对象
import {arrayUtils} from '.../lib/es6-functional.js'

let getTopTenSubRedditData = (type)=>{
    let response = getTopTenSubRedditPosts(type);
    return MayBe.of(response)
        //函数序列
        .map((arr)=>arr['data'])
        .map((arr)=>arr['children'])
        //遍历children 并且只返回title和URL
        .map((arr)=>arrayUtils.map(arr,(x)=>{
            return {
                titile:x['data'].title,
                url:x['data'].url
            }
        }))
}

Either函子

Either函子能够解决分支拓展问题(branching-out problem)
给出一个上下文,看一下上节的例子

MayBe.of("George")
     .map(()=>undefined)
     .map((x)=>"Mr."+x)

     //返回如下结果
     MayBe {value:null}

实现Either函子

const Nothing = function(val){
    this.value = val;
};
Nothing.of = function(val){
    return new Nothing(val);
}

//返回对象本身
Nothing.prototype.map = function(f){
    return this;
};

const Some = function(val){
    this.value = val;
};

Some.of = function(val){
    return new Some(val);
};
//一个container的副本
Some.prototype.map = function(fn){
    return Some.of(fn(this.value));
}
//可以在some上运行函数,但是不能在nothing上面运行。
//eg:
Some.of("test").map((x)=>x.toUpperCase())
=>Some {value:"TEST"}
Nothing.of("test").map((x)=>x.toUpperCase())
=>Nothing {value:"test"}
  • Either定义
const Either = {
    Some:Some,
    Nothing:Nothing
}

reddit例子的Either版本

let getTopTenSubRedditData = (type)=>{
    let response = getTopTenSubRedditPosts(type);
    return MayBe.of(response).map((arr)=>arr['data'])
                             .map((arr)=>arr['children'])
                             .map((arr)=>arrayUtils.map(arr,(x)={return {
                                 title:x['data'].title,
                                 url:x['data'].url
                             }}))
}
//传入错误l类型
getTopTenSubRedditData('unknow')
=>MayBe(value:null)
  • 使Either获取Reddit子版块的Top10帖子
let getTopTenSubRedditPostsEither = (type)=>{
    let response
    try{
        //封装正确响应
        response = Some.of(JSON.parse(request('get','https://www.reddit.com/r/subreddits'+type+".json?limit=10").getBody('utf8')))
    }catch(err){
        //封装错误响应
        response = Nothing.of((message:"Something went wrong",errorCode:err['statusCode']))
    }
    return response;
}
  • 将Reddit API修改如下
let getTopTenSubRedditDataEither=(type)=>{
    let response = getTopTenSubRedditPostsEither(type);
    return response.map((arr)=>arr['data'])
                   .map((arr)=>arr['children'])
                   .map((arr)=>arrayUtils.map(arr,(x)={
                       return {
                           title:x['data'].title,
                           url:x['data'].url
                       }
                   }))
}
//使用错误的Reddit数据调用新的api
getTopTenSubRedditDataEither('new2')
//返回
Nothing(value:{message:'Something went wrong',errorCode:404})

使用either获得了分支失败的确切原因,在getTopTenSubRedditEither返回Nothing,因此函数永远不会执行

你可能感兴趣的:(JavaScript,Web前端)