函数式编程相识一下(下)

GitHub原文地点:https://github.com/Nealyang.md)

回忆柯里化、偏运用

函数式编程相识一下(上)

关于上一篇文章,有朋侪群里艾特说不是很邃晓柯里化的函数,这里我们拿出来简朴说下

let curry = (fn) =>{
    if(typeof fn !== 'function'){
        throw Error('No Function');
    }

    return function curriedFn(...args){
        if(args.length < fn.length){
            return function(){
                return curriedFn.apply(null,args.concat([].slice.call(arguments)));
            }
        }
        return fn.apply(null,args);
    }
}

function add (a,b,c) { return a+b+c }

curry(add)(1)(2)(3)

一步一步来明白,第一次挪用curry函数的时刻,返回一个curried函数,待挪用状况,当我们传入1的时刻,返回的照旧是一个函数,args是应用闭包,纪录你传入的参数是不是为函数定义时刻的参数个数,假如不是,那我接着守候你在传入。因为我们应用args来纪录每次传入的值,所以我们每次拿curry函数后的传入的参数就必须运用arguments了,因为它是类数组,我们想拿到参数值,所以这里我们运用slice。终究,我们实在照样挪用a+b+c的运算。

同理,偏运用的存在实在就是弥补了柯里化传参递次的短板

const partial = function (fn,...partialArgs){
  let args = partialArgs;
  return function(...fullArgs){
    let arg = 0;
    for(let i = 0; i<args.length && fullArgs.length;i++){
      if(arg[i] === undefined){
        args[i] = fullArgs[arg++];
      }
    }
    return fn.apply(null,args)
  }
}

let delayTenMs = partial(setTimeout , undefined , 10);

delayTenMs(() => console.log('this is Nealyang'));

一样应用闭包存储参数,应用undefined来占位

组合、管道

观点

官方诠释为,函数式编程中的函数组合被称之为组合。说的云里雾里的,实在就是多个函数一同完成一件事,组合嘛。那管道呢?咱浅显点,类似gulp的pipe观点,你处置惩罚完了,吐出来,我接着处置惩罚(此处不禁想起人体蜈蚣,哇~),咳咳,正式点,将最左边的函数输出所为输入发送给右边函数,从技术上来讲,就是管道。

为何要如许呢?实在照样我们之前说的,函数的准绳就是小、单一、简朴。因为易测、简朴。而我们呢,经由过程组合运用这些简朴的函数而完成一个不简朴的函数,完成一个不简朴的功用。是不是是类似于React编写组件的观点。经由过程组合种种小组件完成页面编写的觉得?

bingo~

compose 函数的完成

先看一个简答的完成

const compose = (a,b)=>(c)=>a(b(c));

let splitIntoSpaces = (str) => str.split(" ");

let count = (array) => array.length;

const countWords = compose(count,splitIntoSpaces);

countWords('Hello , I am Nealyang');

在后面的开辟中,我们只须要经由过程countWords就可以统计出单词的数目,经由过程这类体式格局完成的也非常的文雅。

实在这类编写的技能就是将多个小而巧的函数组合完成不一样的功能出来。举个栗子:

let map = (array,fn) => {
  let results = []
  for(const value of array)
      results.push(fn(value))

  return results;  
};
let filter = (array,fn) => {
  let results = []
  for(const value of array)
     (fn(value)) ? results.push(value) : undefined

  return results;  
};
let apressBooks = [
    {
        "id": 111,
        "title": "C# 6.0",
        "author": "ANDREW TROELSEN",
        "rating": [4.7],
        "reviews": [{good : 4 , excellent : 12}]
    },
    {
        "id": 222,
        "title": "Efficient Learning Machines",
        "author": "Rahul Khanna",
        "rating": [4.5],
        "reviews": []
    },
    {
        "id": 333,
        "title": "Pro AngularJS",
        "author": "Adam Freeman",
        "rating": [4.0],
        "reviews": []
    },
    {
        "id": 444,
        "title": "Pro ASP.NET",
        "author": "Adam Freeman",
        "rating": [4.2],
        "reviews": [{good : 14 , excellent : 12}]
    }
];

const compose = (a, b) =>
  (c) => a(b(c))

const partial = function (fn,...partialArgs){
  let args = partialArgs.slice(0);
  return function(...fullArguments) {
    let arg = 0;
    for (let i = 0; i < args.length && arg < fullArguments.length; i++) {
      if (args[i] === undefined) {
        args[i] = fullArguments[arg++];
        }
      }
      return fn.apply(this, args);
  };
};

console.log("挑选效果",map(filter(apressBooks, (book) => book.rating[0] > 4.5),(book) => {
    return {title: book.title,author:book.author}
}))
//东西类函数
let filterOutStandingBooks = (book) => book.rating[0] === 5;
let filterGoodBooks = (book) =>  book.rating[0] > 4.5;
let filterBadBooks = (book) => book.rating[0] < 3.5;

let projectTitleAndAuthor = (book) => { return {title: book.title,author:book.author} }
let projectAuthor = (book) => { return {author:book.author}  }
let projectTitle = (book) => { return {title: book.title} }

let queryGoodBooks = partial(filter,undefined,filterGoodBooks);
let mapTitleAndAuthor = partial(map,undefined,projectTitleAndAuthor)

let titleAndAuthorForGoodBooks = compose(mapTitleAndAuthor,queryGoodBooks)

console.log(titleAndAuthorForGoodBooks(apressBooks))

let mapTitle = partial(map,undefined,projectTitle)
let titleForGoodBooks = compose(mapTitle,queryGoodBooks)

//console.log(titleForGoodBooks(apressBooks))

经由过程如上的代码,我们可以很轻松的看出经由过程组合这些小函数,而完成许多功用。非常的天真。

多个函数的组合

当前版本的compose只完成了俩个函数的组合,那末假如关于多个函数呢?

const compose = (...fns) => (value) => reduce(fns.reverse(),(acc , fn ) => fn(acc),value);

上面最主要的一行是

reduce(fns.reverse(),(acc , fn ) => fn(acc),value)

此处我们起首fns.reverse()反转了函数数组,并传入了函数(acc,fn)=>fn(acc) ,它会以传入的acc作为其参数顺次挪用每个函数。很显然,累加器的初始值为value,它将作为函数的第一个输入。

const composeN = (...fns) =>
  (value) =>
    reduce(fns.reverse(),(acc, fn) => fn(acc), value);

const pipe = (...fns) =>
  (value) =>
    reduce(fns,(acc, fn) => fn(acc), value);

let oddOrEven = (ip) => ip % 2 == 0 ? "even" : "odd"
var oddOrEvenWords = composeN(oddOrEven,count,splitIntoSpaces);
let count = (array) => array.length;
console.log(oddOrEvenWords("hello your reading about composition"))

oddOrEvenWords = pipe(splitIntoSpaces,count,oddOrEven);
console.log(oddOrEvenWords("hello your reading about composition"))

如上的代码,有无发明composeN和pipe非常的类似?实在就是实行序列的差别罢了。从左至右处置惩罚数据流我们称之为管道。

函子

观点

在编写代码中的时刻,我们肯定会涉及到关于毛病的处置惩罚,而我们如今涉及到的新名词:函子,实在也不是什么嵬峨上的东西,简朴的说就是在函数式编程中的一种毛病处置惩罚体式格局。我们用这类纯函数的体式格局来协助我们处置惩罚毛病。

函子是一个平常对象,它完成了map函数,在遍历每个对象的时刻天生新的对象

一步步梳理观点

起首我们可以将函子明白为容器。

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

优化上面的容器,我们给Container增加一个of的静态要领,就是用来建立对象的

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

到这一步,我们再转头看观点,函子是一个平常对象,它完成了一个map函数。。。,所以下一步,我们就来完成一个map函数吧

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

如上,我们就编写除了一个函子,是不是是也就那末回事?所以有哥们会问了,咱编写这个干吗呢?有啥用?啊哈,咱接着往下看呗

MayBe 函子

MayBe函子可以让我们可以以越发函数式的体式格局处置惩罚毛病

简朴的看下详细的完成吧

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

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

MayBe.prototype.isNothing = function() {
 return (this.value === null || this.value === undefined);
};

MayBe.prototype.map = function(fn) {
 return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this.value));
};

console.log("MayBe chaining",MayBe.of("George")
    .map((x) => x.toUpperCase())
    .map((x) => "Mr. " + x))

console.log("MayBe chaining null",
   MayBe.of("George")
    .map(() => undefined)
    .map((x) => "Mr. " + x))

如上代码的实行效果为:
《函数式编程相识一下(下)》

### MayBe函子的用处

在说用处之前呢,我们可以看一下在之前处置惩罚接口返回数据的平常逻辑(hack体式格局)

let value = 'string';
if(value != null || value != undefind){
 return value.toupperCase();
}

//实际项目中的例子
 getPageModuleData = () => {
   return this.getDataFromXctrl(pageData.moduleData).then(moduleData => {
     if (moduleData.filter.data.hotBrands.length) {
       this.setState({
         moduleData: moduleData.filter.data
       });
     }
     // 小于多少个拍品,举行引荐
     if (
       moduleData &&
       moduleData.list &&
       moduleData.list.data &&
       moduleData.list.data.settings &&
       moduleData.list.data.settings.length
     ) {
       this.recLimit = moduleData.list.data.settings[0].showRecLimit;
     }
     if (!this.recLimit) {
       this.recLimit = 5; // 兜底
     }
   });
 };

对,这类敕令式的体式格局老是把一些没必要要的逻辑暴露出来,运用MayBe函子就不会有这个题目

他的操纵,会让你觉得非常的惬意

MayBe.of('Nealyang')
 .map((x)=>x.toUpperCase())
 .map(x=>`Mr.${x}`);

烦琐了这么多,我们就为了申明两个MayBe函子主要的属性

1: 纵然给map传入返回null或许undefined的函数,MayBe也照旧可以处置惩罚

2:一切的map函数都邑挪用,不管他是不是接收到null or undefined

实际操刀

说了这么多,那末在我们的一样平常开辟中,我们MayBe究竟怎样运用呢。这里我们照样拿项目中罕见的要求接口来举栗子~

《函数式编程相识一下(下)》

var request = require('sync-request');
...

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
}

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
                                    } 
                                }
                            ))
}

console.log("准确的接收到返回:",getTopTenSubRedditData('new'))
console.log("毛病时刻的状况",getTopTenSubRedditData('neww'))
//MayBe{value:[{title:...,url:...},{}...]}

如上,我们要求一个接口,然后一样平常处置惩罚接口返回数据,并不须要去忧郁值是不是存在而致使顺序非常~
《函数式编程相识一下(下)》

Either函子

上面,我们可以准确的处置惩罚数据了,然则毛病的数据呢?我们须要将毛病信息跑出给出提醒,这也是我们罕见的需求,然则运用MayBe函子就不可以很好地定位到毛病的分支究竟在哪了。!!!哇,搞了半天,你MayBe不咋地啊~ 实在不然,只是差别的函子有自身差别的着重,在这个时刻,我们就须要一个越发壮大的MayBe函子了: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);
};

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

const Either = {
  Some : Some,
  Nothing: Nothing
}

上面我们写了两个函数,Some和Nothing,Some简朴易懂,我们来讲说Nothing,他也是一个Container,然则其map不实行指定的函数,而是直接返回对象自身。直接的说就是一些函数可以在Some上运转然则不能再Nothing中运转

console.log("Something example", Some.of("test").map((x) => x.toUpperCase()))
console.log("Nothing example", Nothing.of("test").map((x) => x.toUpperCase()))

比较简朴,在实际的运用中,我们只须要简朴修正response的处置惩罚体式格局即可

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
}

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
                                    } 
                                }
                            ))
}

console.log("准确的运转: ",getTopTenSubRedditDataEither('new'))
console.log("毛病:",getTopTenSubRedditDataEither('new2'))//Nothing{value:{ message: "Something went wrong" , errorCode: err['statusCode'] }}

题外话

假如人人对Java有些相识的话,一定会发明这个跟Java8 中Optional非常的类似。实在Optional就是一个函子~ 《函数式编程相识一下(下)》

末了谈一谈Monad

观点

直接点,Monad实在也是一个函子,存期近合理,咱来讲一说他究竟是一个啥样子的函子。如今我们的需求是猎取Reddit的批评,固然,我们可以运用MayBe函子来搞定的,稍后我们来看下完成。只不过,这里须要申明的是,MayBe函子越发的专注题目自身,而没必要体贴没必要要的贫苦比方undefined或许null

需求

该需求分为两步:

《函数式编程相识一下(下)》

《函数式编程相识一下(下)》

我们须要猎取批评对象后,将我们须要的title兼并效果并返回新对象:{title:…,comments:[Object,Object,…]}

MayBe 版本完成

第一步的完成

let searchReddit = (search) => {
    let response  
    try{
       response = JSON.parse(request('GET',"https://www.reddit.com/search.json?q=" + encodeURI(search) + "&limit=2").getBody('utf8'))
    }catch(err) {
        response = { message: "Something went wrong" , errorCode: err['statusCode'] }
    }
    return response
}

let getComments = (link) => {
    let response
    try {
        console.log("https://www.reddit.com/" + link)
        response = JSON.parse(request('GET',"https://www.reddit.com/" + link).getBody('utf8'))
    } catch(err) {
        console.log(err)
        response = { message: "Something went wrong" , errorCode: err['statusCode'] }
    }

    return response 
}

上面代码就是完成了两个要求api。详细完成不诠释了,非常简朴。

第二步的完成

let mergeViaMayBe = (searchText) => {
    let redditMayBe = MayBe.of(searchReddit(searchText))
    let ans = redditMayBe
               .map((arr) => arr['data'])
               .map((arr) => arr['children'])
               .map((arr) => arrayUtils.map(arr,(x) => {
                        return {
                            title : x['data'].title,
                            permalink : x['data'].permalink
                        }
                    } 
                ))
               .map((obj) => arrayUtils.map(obj, (x) => {
                    return {
                        title: x.title,
                       comments: MayBe.of(getComments(x.permalink.replace("?ref=search_posts",".json")))
                    }
               }))

   return ans;
}

《函数式编程相识一下(下)》

说说题目

是的,我们处理了我们的需求,然则仔细看上面代码,貌似丧失我们运用函子的初志:代码简约,看着爽~ 而上面的map多到疑心人生,自身写起来可能会很好,然则他人保护起来是一个非常头疼的事变!

最头痛的时刻,运转上面的函数后,我们拿到的值也是函子套函子,所以,该怎样处理呢?这就是我们要说的Monad函子的用处

let answer = mergeViaMayBe("functional programming")

console.log(answer)

/*
    须要两次map才拿到我们想要的
*/
answer.map((result) => {
    arrayUtils.map(result,(mergeResults) => {
        mergeResults.comments.map(comment => {
            console.log(comment)
        })
    }) 
})

在我们猎取Components的时刻,他也是一个函子,所以我们得运用map

简朴的把题目睁开是酱紫的:

let example=MayBe.of(MayBe.of(5));
//将value 加 4 的需求
example.map(x=>x.map(v=>v+4))
//MayBe{value:MayBe{value:9}}

获得的效果照样套两层,+4的需求贫苦,获得的效果嵌套也贫苦,那末是不是可以将两层,扒开呢????
《函数式编程相识一下(下)》

join 来也

来的目的很简朴,扒开嵌套!!!

直接看完成:

MayBe.prototype.join = function(){
  return this.isNothing?MayBe.of(null):this.value
}

搞定!

在转头看上面的需求:

let example=MayBe.of(MayBe.of(5));
example.join().map(v=>v+4);//=> MayBe(value:9)

搞定!!!

再转头看上上面的需求:

let mergeViaJoin = (searchText) => {
    let redditMayBe = MayBe.of(searchReddit(searchText))
    let ans = redditMayBe.map((arr) => arr['data'])
               .map((arr) => arr['children'])
               .map((arr) => arrayUtils.map(arr,(x) => {
                        return {
                            title : x['data'].title,
                            permalink : x['data'].permalink
                        }
                    } 
                ))
               .map((obj) => arrayUtils.map(obj, (x) => {
                    return {
                        title: x.title,
                       comments: MayBe.of(getComments(x.permalink.replace("?ref=search_posts",".json"))).join()
                    }
               }))
               .join()

   return ans;
}

let answer = mergeViaJoin("functional programming")

console.log(answer)

如上代码,我们在函子后增加了两个join,胜利的处理了函子套函子的题目。

对的,上面的join确实到场的体式格局有点为难~~~~ OK~我们在革新革新。

现在,我们老是要在map后挪用join要领,下面我们把逻辑封装到一个名为chain中

MayBe.prototype.chain = function(f){
  return this.map(f).join()
}
...
let mergeViaChain = (searchText) => {
    let redditMayBe = MayBe.of(searchReddit(searchText))
    let ans = redditMayBe.map((arr) => arr['data'])
               .map((arr) => arr['children'])
               .map((arr) => arrayUtils.map(arr,(x) => {
                        return {
                            title : x['data'].title,
                            permalink : x['data'].permalink
                        }
                    } 
                ))
               .chain((obj) => arrayUtils.map(obj, (x) => {
                    return {
                       title: x.title,
                       comments: MayBe.of(getComments(x.permalink.replace("?ref=search_posts",".json"))).chain(x => {
                            return x.length
                       })
                    }
               }))

   return ans;
}

//trying our old problem with chain
answer = mergeViaChain("functional programming")

console.log(answer)

《函数式编程相识一下(下)》

什么是Monad

烦琐了这么多,所以究竟什么是Monad呢?貌似我们一直以来都在处理题目,这类觉得就像实际中,这个人很面熟了,然则。。。还不晓得怎样称谓一样。为难~

OK,Monad就是一个含有chain要领的函子,这就是Monad!(是不是是觉得这个定义非常的山寨,哈哈)

如你所见,我们经由过程增加一个chain(固然也包含join)来睁开MayBe函子,是其成为了一个Monad!

这类觉得就像~给自行车加了个电瓶,他就叫电瓶车了一样,哈啊

结束语

函数式编程,意在通知我们运用数学式函数头脑来处理题目,别忘了我们的准绳:最小单一准绳!

我也还在进修的路上,不当的处所,还愿望多多指教~

    原文作者:isNealyang
    原文地址: https://segmentfault.com/a/1190000015597564
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞