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