摘要:一直以來沒有對函數(shù)式編程有一個(gè)全面的學(xué)習(xí)和使用,或者說沒有一個(gè)深刻的思考。是不是輕松了其實(shí)函數(shù)式編程主張的就是以抽象的方式創(chuàng)建函數(shù)。后面咱們在系統(tǒng)性的學(xué)習(xí)下函數(shù)式編程。
一直以來沒有對函數(shù)式編程有一個(gè)全面的學(xué)習(xí)和使用,或者說沒有一個(gè)深刻的思考。最近看到一些博客文章,突然覺得函數(shù)式編程還是蠻有意思的。看了些書和文章。這里記載下感悟和收獲。 歡迎團(tuán)隊(duì)姜某人多多指點(diǎn)@姜少。 由于博客秉持著簡短且全面原則。遂分為上下兩篇部分簡介原文地址 Nealyang
### 函數(shù)式編程了解一下(上)
入門簡介
HOC簡介
函數(shù)柯里化與偏應(yīng)用
函數(shù)式編程了解一下(下)組合與管道
函子和Monad
再回首Generator
入門簡介函數(shù)的第一原則是要小,函數(shù)的第二原則是要更小什么是函數(shù)式編程?為什么他重要
在理解什么是函數(shù)式編程的開始,我們先了解下什么數(shù)學(xué)中,函數(shù)具有的特性
函數(shù)必須總是接受一個(gè)參數(shù)
函數(shù)必須總是返回一個(gè)值
函數(shù)應(yīng)該依據(jù)接受到的參數(shù),而不是外部的環(huán)境運(yùn)行
對于一個(gè)指定的x,必須返回一個(gè)確定的y
所以我們說,函數(shù)式編程是一種范式,我們能夠以此創(chuàng)建僅依賴輸入就可以完成自身邏輯的函數(shù)。這保證了當(dāng)函數(shù)多次調(diào)用時(shí),依然可以返回相同的結(jié)果。因此可以產(chǎn)生可緩存的、可測試的代碼庫
引用透明所有的函數(shù)對于相同的輸入都返回相同的結(jié)構(gòu),這一特性,我們稱之為引用透明。
比如:
let identity = (i) => {return i};
這么簡單?對,其實(shí)就是這樣,也就是說他沒有依賴任何外部變量、外部環(huán)境,只要你給我東西,我經(jīng)過一頓鼓搗,總是給你返回你所能預(yù)測的結(jié)果。
這也為我們后面的并發(fā)代碼、緩存成為可能。
命令式、聲明式和抽象函數(shù)式編程主張聲明式編程和編寫抽象代碼。其實(shí)這個(gè)比較有意思,感覺更像是面向?qū)ο蟮木幊獭?/p>
光說不練都是扯淡。舉個(gè)栗子
var array = [1,2,3,4,5,6]; for(let i = 0;i這段代碼的作用簡單明了,就是遍歷!但是你有沒有感覺這個(gè)代碼呆呆的。沒有一丁點(diǎn)的靈氣?都是我告訴你該怎么該怎么做的。我們告訴編譯器,你先去獲取下數(shù)組的長度的,然后挨個(gè)log出來。這種編碼方式,我們通常稱之為“命令式”解決方案。
而在函數(shù)式編程中,我們其實(shí)更加主張用“聲明式”解決方案
let array = [1,2,3,4,5,6]; array.forEach(item=>{console.log(item)})簡單體會(huì)下,是不是有那么一丟丟的靈感來了?等等,你這個(gè)forEach函數(shù)哪來的嘛!對,也是自己寫的,但是不是我們通過編寫這種抽象邏輯代碼,而讓整體的業(yè)務(wù)代碼更加的清晰明了了呢?開發(fā)者是需要關(guān)心手頭上的問題就好了,只需要告訴編譯器去干嘛而不是怎么干了。是不是輕松了?
其實(shí)函數(shù)式編程主張的就是以抽象的方式創(chuàng)建函數(shù)。這些函數(shù)可以在代碼的其他部分被重用。
函數(shù)式編程的好處好處個(gè)人不喜歡扯太多,不是因?yàn)樗麤]有好處,而是對于剛剛接觸函數(shù)式編程的哥們,上來就說好處其實(shí)是沒什么概念的,所以這里我簡單提一提,后面文章會(huì)細(xì)細(xì)說明。
純函數(shù) => 可緩存熟悉redux的同學(xué)應(yīng)該對這個(gè)詞語都不陌生,所謂的純函數(shù),其實(shí)也就是我們說的引用透明,穩(wěn)定輸出!好處呢?可預(yù)測嘛,容易編寫測試代碼哇,可緩存嘛。什么是可緩存?可以看我之前發(fā)的文章哈,這里簡單舉個(gè)栗子
let longRunningFunction = (input)=>{ //進(jìn)行了非常麻煩的計(jì)算,然后返回出來結(jié)果 return output; }如果longRunningFunction是一個(gè)純函數(shù),引用透明。我們就可以說對于同樣的輸出,總是返回同樣的結(jié)果,所以我們?yōu)槭裁床荒軌蜻\(yùn)用一個(gè)對象將我們每一次的運(yùn)算結(jié)果存起來呢?
let longRunningFunctionResult = {1:2,2:3,3:4}; //檢查key是否存在,存在直接用,不存在再計(jì)算 longRunningFunctionResult.hasOwnProperty(input)?longRunningFunctionResult[input]:longRunningFunctionResult[input] = longRunningFunction(input)比較直觀。不多說了哈。其實(shí)好處還有之前說到的并發(fā)。不說的這么冠冕堂皇了,啥并不并發(fā)呀,我不依賴別人的任何因素,只依據(jù)你的輸出我產(chǎn)出。你說我支持什么就是什么咯,只要你給我對的參數(shù)傳進(jìn)來就可以了。
結(jié)束語匆匆收尾!僅作為拋磚引玉。后面咱們在系統(tǒng)性的學(xué)習(xí)下函數(shù)式編程。
高階函數(shù)(HOC)簡介 概念JavaScript作為一門語言,將函數(shù)視為數(shù)據(jù)。允許函數(shù)代替數(shù)據(jù)傳遞是一個(gè)非常強(qiáng)大的概念。接受一個(gè)函數(shù)作為參數(shù)的函數(shù)成為高階函數(shù)(Higher-Order Function)
從數(shù)據(jù)入門HOCJavaScript支持如下幾種數(shù)據(jù)類型:
Number
String
Boolean
Object
null
undefined
這里面想強(qiáng)調(diào)的是JavaScript將函數(shù)也同樣是為一種數(shù)據(jù)類型。當(dāng)一門語言允許將函數(shù)作為數(shù)據(jù)那樣傳遞和使用的時(shí)候,我們就稱函數(shù)為一等公民。
所以說這個(gè)就是為了強(qiáng)調(diào)說明,在JavaScript中,函數(shù)可以被賦值,作為參數(shù)傳遞,也可以被其他函數(shù)返回。
//傳遞函數(shù) let tellType = (arg)=>{ if(typeof arg === "function"){ arg(); }else{ console.log(`this data is ${arg}`) } } let dataFn = ()=> { console.log("this is a Function"); } tellType(dataFn);//返回函數(shù) let returnStr = ()=> String; returnStr()("Nealyang") //let fn = returnStr(); //fn("Nealyang");從上我們可以看到函數(shù)可以接受另一個(gè)函數(shù)作為參數(shù),同樣,函數(shù)也可以將兩一個(gè)函數(shù)作為返回值返回。
所以高階函數(shù)就是接受函數(shù)作為參數(shù)并且/或者返回函數(shù)作為輸出的函數(shù)HOC 到底你是干嘛的當(dāng)我們了解到如何去創(chuàng)建并執(zhí)行一個(gè)高階函數(shù)的時(shí)候,同行我們都想去了解,他到底是干嘛的?OK,簡單的說,高階函數(shù)常用于抽象通用的問題。換句話說,高階函數(shù)就是定義抽象。簡單的說,其實(shí)就類似于命令式的編程方式,將具體的實(shí)現(xiàn)細(xì)節(jié)封裝、抽象起來,讓開發(fā)者更加的關(guān)心業(yè)務(wù)。抽象讓我們專注于預(yù)定的目標(biāo)而不是去關(guān)心底層的系統(tǒng)概念。
理解這個(gè)概念非常重要,所以下面我們將通過大量的栗子來說明
舉斤栗子const every = (arr,fn)=>{ let result = true; for(const value of arr){ result = result && fn(value); } return result; } every([NaN,NaN,4],isNaN); const some = (arr,fn)=>{ let result = true; for(const value of arr){ result = result || fn(value); } return result; } some([3,1,2],isNaN); //這里都是低效的實(shí)現(xiàn)。這里主要是理解高階函數(shù)的概念let sortObj = [ {firstName:"aYang",lastName:"dNeal"}, {firstName:"bYang",lastName:"cNeal"}, {firstName:"cYang",lastName:"bNeal"}, {firstName:"dYang",lastName:"aNeal"}, ]; const sortBy = (property)=>{ return (a,b) => { return (a[property]b[property])?1:0 } } sortObj.sort(sortBy("lastName")); //sort函數(shù)接受了被sortBy函數(shù)返回的比較函數(shù),我們再次抽象出compareFunction的邏輯,讓用戶更加關(guān)注比較,而不用去在乎怎么比較的。HOC必然離不開閉包上面的sortBy其實(shí)大家都應(yīng)該看到了閉包的蹤影。關(guān)于閉包的產(chǎn)生、概念這里就不啰嗦了。總之我們知道,閉包非常強(qiáng)大的原因就是它對作用域的訪問。
簡單說下閉包的三個(gè)可訪問的作用域:
在它自身聲明之內(nèi)的變量
對全局變量的訪問
對外部函數(shù)變量的訪問(*)
接著舉栗子const forEach = (arr,fn)=>{ for(const item of arr){ fn(item); } } //tap接受一個(gè)value,返回一個(gè)帶有value的閉包函數(shù) const tap = (value)=>(fn)=>{ typeof fn === "function"?fn(value):console.log(value); } forEach([1,2,3,4,5],(a)=>{ tap(a)(()=>{ console.log(`Nealyang:${a}`) }) });函數(shù)柯里化與偏應(yīng)用 函數(shù)柯里化 概念直接看概念,柯里化是把一個(gè)多參函數(shù)轉(zhuǎn)換為一個(gè)嵌套的一元函數(shù)的過程
不理解,莫方!舉個(gè)栗子就明白了。
假設(shè)我們有一個(gè)函數(shù),add:
const add = (x,y)=>x+y;我們調(diào)用的時(shí)候當(dāng)然就是add(1,2),沒有什么特別的。當(dāng)我們柯里化了以后呢,就是如下版本:
const addCurried = x => y => x + y;調(diào)用的時(shí)候呢,就是這個(gè)樣子的:
addCurried(4)(4)//8是不是非常的簡單?
說到這,我們在來回顧下,柯里化的概念:把一個(gè)多參函數(shù)轉(zhuǎn)換成一個(gè)嵌套的一元函數(shù)的過程。
如何實(shí)現(xiàn)多參函數(shù)轉(zhuǎn)為一元上面的代碼中,我們實(shí)現(xiàn)了二元函數(shù)轉(zhuǎn)為一元函數(shù)的過程。那么對于多參我們該如何做呢?
這個(gè)是比較重要的部分,我們一步一步來實(shí)現(xiàn)
我們先來添加一個(gè)規(guī)則,最一層函數(shù)檢查,如果傳入的不是一個(gè)函數(shù)來調(diào)用curry函數(shù)則拋出錯(cuò)誤。當(dāng)如果提供了柯里化函數(shù)的所有參數(shù),則通過使用這些傳入的參數(shù)調(diào)用真正的函數(shù)。
let curry = (fn) => { if(typeof fn !== "function"){ throw Error("not a function"); } return function curriedFn (...args){ return fn.apply(null,args); } }所以如上,我們就可以這么玩了
const multiply = (x,y,z) => x * y * z; curry(multiply)(1,2,3);//6革命還未成功,我們繼續(xù)哈~下面我們的目的就是把多參函數(shù)轉(zhuǎn)為嵌套的一元函數(shù)(重回概念)
const multiply = (x,y,z) => x * y * z; let curry = (fn) => { if(typeof fn !== "function"){ throw Error("not a 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); } } curry(multiply)(1)(2)(3)如果是初次看到,可能會(huì)有些疑惑。我們一行行來瞅瞅。
args.length < fn.length這段代碼比價(jià)直接,就是判斷,你傳入的參數(shù)是否小于函數(shù)參數(shù)長度。
args.concat([].slice.call(arguments))我們使用cancat函數(shù)鏈接一次傳入的一個(gè)參數(shù),并遞歸調(diào)用curriedFn。由于我們將所有的參數(shù)傳入組合并遞歸調(diào)用,最終if判斷會(huì)失效,就返回結(jié)果了。
小小實(shí)操一下我們寫一個(gè)函數(shù)在數(shù)組內(nèi)容中查找到包含數(shù)字的項(xiàng)
let curry = (fn) => { if(typeof fn !== "function"){ throw Error("not a 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); } } let match = curry(function(expr,str){return str.match(expr)}); let hasNumber = match(/[0-9]+/); let filter = curry(function(f,ary){ return ary.filter(f) }); filter(hasNumber)(["js","number1"]);通過如上的例子,我想我們也應(yīng)該看出來,為什么我們需要函數(shù)的柯里化:
程序片段越小越容易被配置
盡可能的函數(shù)化
偏應(yīng)用假設(shè)我們需要10ms后執(zhí)行某一個(gè)特定操作,我們一般的做法是
setTimeout(() => console.log("do something"),10); setTimeout(() => console.log("do other thing"),10);如上,我們調(diào)用函數(shù)都傳入了10,能使用curry函數(shù)把他在代碼中隱藏嗎?我擦,咱curry多牛逼!肯定不行的嘛~
因?yàn)閏urry函數(shù)應(yīng)用參數(shù)列表是從最左到最右的。由于我們是根據(jù)需要傳遞函數(shù),并將10保存在常量中,所以不能以這種方式使用curry。我們可以這么做:
const setTimeoutFunction = (time , fn) => { setTimeout(fn,time); }但是如果這樣的話,我們是不是太過于麻煩了呢?為了減少了10的傳遞,還需要多造一個(gè)包裝函數(shù)?
這時(shí)候,偏應(yīng)用就出來了!!!
簡單看下代碼實(shí)現(xiàn):
const partial = function (fn,...partialArgs){ let args = partialArgs; return function(...fullArgs){ let arg = 0; for(let i = 0; iconsole.log("this is Nealyang")); 如上大家應(yīng)該都能夠理解。這里不做過多廢話解釋了。
簡單總結(jié)的說:
所以,像map,filter我們可以輕松的使用curry函數(shù)解決問題,但是對于setTimeout這類,最合適的選擇當(dāng)然就是偏函數(shù)了。總之,我們使用curry或者partial是為了讓函數(shù)參數(shù)或者函數(shù)設(shè)置變得更加的簡單強(qiáng)大。
下節(jié)預(yù)告上一部分說的比較淺顯基礎(chǔ),希望大家也能夠從中感受到函數(shù)式編程的精妙和靈活之處。大神請直接略過~求指正求指導(dǎo)~
下一節(jié)中,將主要介紹下,函數(shù)式編程中的組合、管道、函子以及Monad。最后我們在介紹下es6的Generator,或許我們能從最后的Generator中豁然開朗獲得到很多啟發(fā)哦~~
技術(shù)交流nodejs 技術(shù)交流 群號:698239345
React技術(shù)棧群號:398240621
前端技術(shù)雜談群號:604953717
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/94270.html
摘要:最近在看,順便看了一些函數(shù)式編程,然后半個(gè)國慶假期就沒有了。最開始接觸函數(shù)式編程的時(shí)候,第一個(gè)接觸的概念就是高階函數(shù),和柯里化。所以我覺得最開始學(xué)習(xí)函數(shù)式編程最好先了解一些相關(guān)概念和思想會(huì)比較好。 最近在看Typescript,順便看了一些函數(shù)式編程,然后半個(gè)國慶假期就沒有了。做個(gè)筆記,分幾個(gè)部分寫吧。 最開始接觸函數(shù)式編程的時(shí)候,第一個(gè)接觸的概念就是高階函數(shù),和柯里化。咋一看,這不就...
摘要:函數(shù)是一等公民。其實(shí)閉包本身也是函數(shù)式編程的一個(gè)應(yīng)用。劣勢不能算是嚴(yán)格意義上的函數(shù)式語言,很多函數(shù)式編程的特性并沒有。 隨著大前端時(shí)代的到來,在產(chǎn)品開發(fā)過程中,前端所占業(yè)務(wù)比重越來越大、交互越來越重。傳統(tǒng)的老夫拿起JQuery就是一把梭應(yīng)付當(dāng)下重交互頁面已經(jīng)十分乏力。于是乎有了Angular,React,Vue這些現(xiàn)代框架。 但隨之而來的還有大量的新知識新名詞,如MVC,MVVM,F(xiàn)l...
摘要:函數(shù)是一等公民。其實(shí)閉包本身也是函數(shù)式編程的一個(gè)應(yīng)用。劣勢不能算是嚴(yán)格意義上的函數(shù)式語言,很多函數(shù)式編程的特性并沒有。 隨著大前端時(shí)代的到來,在產(chǎn)品開發(fā)過程中,前端所占業(yè)務(wù)比重越來越大、交互越來越重。傳統(tǒng)的老夫拿起JQuery就是一把梭應(yīng)付當(dāng)下重交互頁面已經(jīng)十分乏力。于是乎有了Angular,React,Vue這些現(xiàn)代框架。 但隨之而來的還有大量的新知識新名詞,如MVC,MVVM,F(xiàn)l...
摘要:前言繼續(xù)向下看廖大教程,看到了函數(shù)式編程這一節(jié),當(dāng)時(shí)是覺得沒啥用直接跳過了,這次準(zhǔn)備要仔細(xì)看一遍了,并記錄下一些心得。 前言 繼續(xù)向下看廖大教程,看到了函數(shù)式編程這一節(jié),當(dāng)時(shí)是覺得沒啥用直接跳過了,這次準(zhǔn)備要仔細(xì)看一遍了,并記錄下一些心得。 函數(shù)式編程 上學(xué)期有上一門叫 人工智能 的課,老師強(qiáng)行要我們學(xué)了一個(gè)叫做 prolog 的語言,哇那感覺確實(shí)難受,思維方式完全和之前學(xué)過的不一樣,...
摘要:在函數(shù)式編程的組合中,我們是從右到左執(zhí)行的,上述的例子中我們借助函數(shù)實(shí)現(xiàn)組合,當(dāng)然,我們也可以用自己的方式實(shí)現(xiàn)。小結(jié)函數(shù)式編程隨著多核的發(fā)展,開始再次出現(xiàn)在我們的視野中,有時(shí)候也會(huì)擔(dān)心過于吹捧函數(shù)式,反而落入俗套。 程序的本質(zhì)是什么?數(shù)據(jù)結(jié)構(gòu)+算法!!!我想這也是很多程序員給出的答案,我自己也認(rèn)可這一觀點(diǎn),當(dāng)我們了解了某一門編程語之后,接下來我們面對的往往是數(shù)據(jù)結(jié)構(gòu)和算法的學(xué)習(xí)。而現(xiàn)在...
閱讀 2760·2021-11-22 14:45
閱讀 906·2021-10-15 09:41
閱讀 1068·2021-09-27 13:35
閱讀 3689·2021-09-09 11:56
閱讀 2634·2019-08-30 13:03
閱讀 3199·2019-08-29 16:32
閱讀 3307·2019-08-26 13:49
閱讀 773·2019-08-26 10:35