函數(shù)式編程初窺

  最近在學(xué)習(xí)Erlang和Python。Erlang是完全的函數(shù)式編程語(yǔ)言,Python語(yǔ)言是面向?qū)ο蟮恼Z(yǔ)言纫骑,但是它的語(yǔ)法引入了大量的函數(shù)式編程思想。越研究越覺(jué)得函數(shù)式的編程思路可以幫助我們規(guī)避很多Bug珠插,所以在這里對(duì)函數(shù)式編程做一個(gè)簡(jiǎn)要的介紹惧磺。分析函數(shù)式編程的特點(diǎn)、方法論捻撑,使用的技術(shù)磨隘,以及同面向?qū)ο缶幊痰漠愅?/p>

?1缤底、函數(shù)式編程簡(jiǎn)介

背景

  函數(shù)式編程誕生于50多年前。現(xiàn)在越來(lái)越多的人開(kāi)始接受并進(jìn)行函數(shù)式編程的實(shí)踐番捂。不僅最古老的函數(shù)式語(yǔ)言 Lisp 重獲青春个唧,而且新的函數(shù)式語(yǔ)言層出不窮,比如 Erlang设预、clojure徙歼、Scala、F#等等鳖枕。目前最當(dāng)紅的Objective-C, Python魄梯、Ruby、 Javascript都引入了對(duì)函數(shù)式編程的支持宾符。就連老牌的面向?qū)ο蟮?Java酿秸、面向過(guò)程的 PHP, 以及蘋(píng)果最新的swift語(yǔ)言,都忙不迭地加入匿名函數(shù)等機(jī)制魏烫。

  越來(lái)越多的跡象表明辣苏,函數(shù)式編程已經(jīng)不再是學(xué)術(shù)界的最?lèi)?ài),開(kāi)始大踏步地在業(yè)界投入實(shí)用哄褒。 也許繼面向?qū)ο缶幊讨笙◇瘮?shù)式編程會(huì)成為下一個(gè)編程的主流范式。

  說(shuō)到函數(shù)式編程編程就不得不說(shuō)面向?qū)ο竽派摹C嫦驅(qū)ο笫前岩粋€(gè)功能的一組操作和相關(guān)數(shù)據(jù)封裝在一個(gè)對(duì)象里退客,面向?qū)ο笫菍?duì)象滿(mǎn)天飛。函數(shù)式編程是把一個(gè)功能的一個(gè)操作和相關(guān)數(shù)據(jù)封裝在一起链嘀,函數(shù)式編程是函數(shù)滿(mǎn)天飛井辜。函數(shù)式編程比面向?qū)ο蟮膬?yōu)勢(shì)就是粒度更小,生命周期更短管闷。減少bug的有效途徑就是減少變量的生命周期粥脚,縮小模塊的粒度;所以函數(shù)式編程更不容易引入bug包个。

定義

  是一種編程范型刷允,它將計(jì)算機(jī)運(yùn)算視為數(shù)學(xué)上的函數(shù)計(jì)算,并且避免使用程序狀態(tài)以及易變對(duì)象碧囊。函數(shù)編程語(yǔ)言最重要的基礎(chǔ)是λ演算(lambda calculus)树灶。λ演算中最關(guān)鍵的要素就是函數(shù)被當(dāng)作變量處理,能夠參與運(yùn)算糯而。

價(jià)值觀

函數(shù)式編程強(qiáng)調(diào)程序的執(zhí)行結(jié)果比執(zhí)行過(guò)程更重要天通。關(guān)注于描述問(wèn)題,而不是怎么實(shí)現(xiàn)熄驼,隱藏實(shí)現(xiàn)細(xì)節(jié)像寒。

化繁為簡(jiǎn)烘豹。利用若干簡(jiǎn)單的執(zhí)行單元讓計(jì)算結(jié)果不斷漸進(jìn),逐層推導(dǎo)復(fù)雜的運(yùn)算诺祸。

?你的優(yōu)秀和我的人生無(wú)關(guān)携悯,請(qǐng)帶著你的趾高氣揚(yáng)滾蛋吧。

 下面是一個(gè)輸出數(shù)組的例子  

shoplist = ['apple','mango','carrot','banana']print'My shopping list is now', shoplist#輸出#My shopping list is now ['banana', 'carrot', 'mango', 'rice']

  這樣的代碼更易讀筷笨,代碼只是描述在干什么憔鬼,而不是如何做到這點(diǎn)的具體實(shí)現(xiàn)。如果是過(guò)程式編程胃夏,需要一個(gè)for循環(huán)去描述實(shí)現(xiàn)細(xì)節(jié)轴或。

 函數(shù)式編程在解決復(fù)雜運(yùn)算問(wèn)題時(shí),把一個(gè)問(wèn)題分解為若干子問(wèn)題仰禀,逐步求解侮叮。軟件或程序的拼裝會(huì)變得更為簡(jiǎn)單和直觀。使代碼更容易理解悼瘾,方便排查問(wèn)題,并且具有更好的可維護(hù)性和擴(kuò)展性审胸。

 現(xiàn)在有這樣一個(gè)數(shù)學(xué)表達(dá)式:

  (1 + 2) * 3 - 4


  傳統(tǒng)的過(guò)程式編程:

  var a = 1 + 2;

  var b = a * 3;

  var c = b - 4;


  函數(shù)式編程要求使用函數(shù),我們可以把運(yùn)算過(guò)程定義為不同的函數(shù),然后寫(xiě)成下面這樣:

  var result = subtract(multiply(add(1,2), 3), 4);


你的優(yōu)秀和我的人生無(wú)關(guān)亥宿,請(qǐng)帶著你的趾高氣揚(yáng)滾蛋吧。這個(gè)就是函數(shù)式編程的準(zhǔn)則:函數(shù)不受外部變量影響砂沛,不依賴(lài)于外部變量烫扼,也不改變外部變量的值。

  傳統(tǒng)的過(guò)程式編程:

int count;void increment()

{

? ? returen count++;

}

  函數(shù)式編程:

def increment(count):

? returncount+1;

  函數(shù)不訪問(wèn)全局變量碍庵,也不改變?nèi)肿兞俊?/p>



2映企、函數(shù)式編程特性


  ? 封裝、繼承静浴、多態(tài)是面向?qū)ο缶幊痰娜筇匦匝呙ァ:瘮?shù)式編程也有自己的語(yǔ)言特性。

數(shù)據(jù)不可變性(immutable data)多有的變量只可以賦值一次苹享,變量不可變双絮,如果想改變變量就創(chuàng)建一個(gè)新的變量。

  數(shù)據(jù)的不可變性保證了程序是無(wú)狀態(tài)的得问,很多難解的bug往往是由各種復(fù)雜的狀態(tài)引起的囤攀。比如發(fā)現(xiàn)某些情況下程序運(yùn)行有問(wèn)題,是某一個(gè)狀態(tài)引起的宫纬,但是這個(gè)狀態(tài)有100種可能性焚挠,在1000個(gè)地方都有對(duì)這個(gè)狀態(tài)進(jìn)行操作。debug的時(shí)候要?dú)⑷说男亩加辛死焐А?shù)據(jù)不可變性同時(shí)保證了函數(shù)沒(méi)有“副作用”蝌衔,函數(shù)的副作用是指除了返回函數(shù)值外榛泛,還對(duì)主調(diào)用函數(shù)

?

函數(shù)是第一公民(first class method)函數(shù)可以像普通變量一樣去使用。函數(shù)可以像變量一樣被創(chuàng)建胚委,修改挟鸠,并當(dāng)成變量一樣傳遞,返回或是在函數(shù)中嵌套函數(shù)亩冬。

?

引用透明(referential transparency)指的是函數(shù)的運(yùn)行不依賴(lài)于外部變量或“狀態(tài)”艘希,只依賴(lài)于輸入的參數(shù),任何時(shí)候只要參數(shù)相同硅急,調(diào)用函數(shù)所得到的返回值總是相同的覆享。天然適應(yīng)并發(fā)編程,因?yàn)檎{(diào)用函數(shù)的結(jié)果具有一致性营袜,所以根本不需要加鎖撒顿,也就不存在死鎖的問(wèn)題。

?

?尾遞歸化(tail call optimization)因?yàn)楹瘮?shù)調(diào)用要壓棧保存現(xiàn)場(chǎng)荚板,遞歸層次過(guò)深的話凤壁,壓棧過(guò)多會(huì)產(chǎn)生性能問(wèn)題。所以引入尾遞歸優(yōu)化跪另,每次遞歸時(shí)都會(huì)重用棧拧抖,提升性能。

? ? ?把函數(shù)作為參數(shù)傳遞的例子

NSComparisonResult (^cmp)(idobj1,idobj2)? = ^NSComparisonResult(idobj1,id obj2) {

? ? return [obj1 isEqualToString:obj2];

}

NSArray* items = [@"a",@"c",@"d"];

[items sortedArrayUsingComparator:cmp];

  sortedArrayUsingComparator方法負(fù)責(zé)排序免绿,需要把比較的規(guī)則告訴他唧席,將比較方法作為一個(gè)參數(shù)傳到函數(shù)中進(jìn)行運(yùn)算。



?3嘲驾、函數(shù)式編程技術(shù)(方法論)


?映射化簡(jiǎn)(map & reduce)函數(shù)式編程最常見(jiàn)的技術(shù)就是對(duì)一個(gè)集合做Map和Reduce操作淌哟。這比起過(guò)程式的語(yǔ)言來(lái)說(shuō),在代碼上要更容易閱讀辽故。傳統(tǒng)過(guò)程式的語(yǔ)言需要使用for/while循環(huán)徒仓,然后在各種變量中把數(shù)據(jù)倒過(guò)來(lái)倒過(guò)去的

?

管道??? (pipeline)把一組函數(shù)放到一個(gè)數(shù)組或是列表中,然后把數(shù)據(jù)傳給這個(gè)列表誊垢,數(shù)據(jù)就像一個(gè)pipeline一樣順序地被各個(gè)函數(shù)所操作蓬衡,最終得到我們想要的結(jié)果。他的設(shè)計(jì)哲學(xué)就是讓每個(gè)功能就做一件事彤枢,并把這件事做到極致狰晚,軟件或程序的拼裝會(huì)變得更為簡(jiǎn)單和直觀。


?遞歸??? (recursing)遞歸最大的好處就簡(jiǎn)化代碼缴啡,他可以把一個(gè)復(fù)雜的問(wèn)題用很簡(jiǎn)單的代碼描述出來(lái)壁晒。遞歸的精髓是描述問(wèn)題,而這正是函數(shù)式編程的精髓业栅。


?柯里化? (currying)把一個(gè)函數(shù)的多個(gè)參數(shù)分解成多個(gè)函數(shù)秒咐, 然后把函數(shù)多層封裝起來(lái)谬晕,每層函數(shù)都返回一個(gè)函數(shù)去接收下一個(gè)參數(shù)。


?高階函數(shù)(higher order function)把函數(shù)當(dāng)參數(shù)携取,接受一個(gè)函數(shù)作為參數(shù)的函數(shù)就叫高階函數(shù)≡芮現(xiàn)象上就是函數(shù)傳進(jìn)傳出。


 ?map & reduce

  Python代碼:

def toUpper(item)

? ? return item.upper()print map(tuUpper , [“hellow”,”world”])

  將數(shù)組里的所有字符串變?yōu)榇髮?xiě)雷滋,直接使用map不撑,不需要寫(xiě)for循環(huán)。最后輸出 ["HELLO","WORLD"]?


printreduce(lambdax , y : x + y,[1,2,3,4,5])

  將數(shù)組里所有數(shù)值進(jìn)行累加晤斩,相當(dāng)于1+2+3+4+5焕檬,輸出 15。

  lambda是Python的匿名函數(shù)澳泵,lambda x,y:x+y相當(dāng)于def func(x,y):return x+y


管道

  如果有一個(gè)需求找出一組數(shù)中的所有偶數(shù)并對(duì)他們求平方实愚,最后求他們的和,可以分解成三個(gè)步驟:

1)找偶數(shù)

2)求平方

3)累加

def even(nums):

? ? ?returnfilter(lambdax: x%2==0, nums)def square(nums):

? ? returnmap(lambdax: x*x, nums)def? total(nums):

? ? returnreduce(lambdax,y:x+y,nums)

nums = [1,2,3,4,5,6,7,8,9,10]

pipeline = total(square(even(nums)))

  even方法求偶數(shù)兔辅,square求他們的平方腊敲,total方法將他們加在一起。通過(guò)管道的方式把他們串聯(lián)在一起维苔,一個(gè)復(fù)雜的處理就完成了碰辅。

讓每個(gè)方法只做一件事,并把這件事做到極致蕉鸳。


?遞歸

  在其他類(lèi)型的語(yǔ)言中,變量往往用來(lái)保存狀態(tài)忍法。變量不可變潮尝,意味著狀態(tài)不能保存在變量中。函數(shù)式編程使用參數(shù)保存狀態(tài)饿序,最好的例子就是遞歸勉失。

voidfun(constint i)

{

? ? if(i <10&& i >=0)

? ? {

? ? ? ? NSLog(@"i:%d", i);

? ? ? ? fun(i +1);

? ? }

}

  每次狀態(tài)的變化就是值+1。

  

 ?柯里化

  柯里化就是一個(gè)函數(shù)只有一個(gè)參數(shù)原探,那如果需要兩個(gè)參數(shù)怎么辦乱凿,比如兩個(gè)數(shù)相加求和。通過(guò)把一個(gè)參數(shù)封裝成函數(shù)的方式實(shí)現(xiàn)咽弦。

def func(a):

? ? def add(b):

? ? ? ? returna+b

? ? return add

funcA = func(5)printfuncA(10)

  func函數(shù)返回一個(gè)add函數(shù)徒蟆,funcA變量就是一個(gè)a為5的add函數(shù)。print funcA(10)就是向add函數(shù)傳入10型型,最后結(jié)果就是5+10 輸出15段审。


?4、函數(shù)式編程意義


  1闹蒜、代碼簡(jiǎn)潔寺枉,快速開(kāi)發(fā)

  ?函數(shù)式編程大量使用函數(shù)抑淫,減少了代碼的重復(fù),因此程序比較短姥闪,開(kāi)發(fā)速度較快始苇。


  2、接近自然語(yǔ)言筐喳,易于理解

   函數(shù)式編程注重干什么而不是怎么干催式,更容易理解。


  3疏唾、方便代碼的管理和維護(hù)

  ?函數(shù)式編程不依賴(lài)蓄氧、也不會(huì)改變外界的狀態(tài),只要給定輸入?yún)?shù)槐脏,返回的結(jié)果必定相同喉童。因此,每一個(gè)函數(shù)都可以被看做獨(dú)立單元顿天,很有利于進(jìn)行單元測(cè)試 (unit testing)和除錯(cuò)(debugging)堂氯,以及模塊化組合。

  4牌废、易于進(jìn)行并發(fā)開(kāi)發(fā)

函數(shù)式編程因?yàn)樗恍薷淖兞垦拾祝愿静淮嬖?鎖"線程的問(wèn)題,不需要考慮"死鎖"(deadlock)鸟缕。不必?fù)?dān)心一個(gè)線程的數(shù)據(jù)晶框,被另一個(gè)線程修改,所以可以很放心地把工作分?jǐn)偟蕉鄠€(gè)線程中懂从。

?  5授段、代碼熱升級(jí)

  函數(shù)式編程沒(méi)有副作用,只要保證接口不變番甩,內(nèi)部實(shí)現(xiàn)是外部無(wú)關(guān)的侵贵。所以,可以在運(yùn)行狀態(tài)下直接升級(jí)代碼缘薛,不需要重啟窍育,也不需要停機(jī)。Erlang語(yǔ)言早就證明了這一點(diǎn)宴胧,它是瑞典愛(ài)立信公司為了管理電話系統(tǒng)而開(kāi)發(fā)的漱抓,電話系統(tǒng)的升級(jí)當(dāng)然是不能停機(jī)的。


最后恕齐,其實(shí)使用面向?qū)ο蠡蛘呙嫦蚍椒ǘ疾恢匾尚匾氖侨绾卫斫馄渲械膬r(jià)值觀和方法論,構(gòu)造可維護(hù)、可擴(kuò)展补胚、穩(wěn)定又靈活的程序码耐。不管白貓黑貓抓到老鼠就是好貓。


 ?參考:

http://en.wikipedia.org/wiki/Functional_programming

http://coolshell.cn/articles/10822.html

http://www.2cto.com/shouce/pythonjc/index.html

http://www.ruanyifeng.com/blog/2012/04/functional_programming.html

https://github.com/zwaldowski/BlocksKit

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末溶其,一起剝皮案震驚了整個(gè)濱河市骚腥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瓶逃,老刑警劉巖束铭,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異厢绝,居然都是意外死亡契沫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)昔汉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)懈万,“玉大人,你說(shuō)我怎么就攤上這事靶病』嵬ǎ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵娄周,是天一觀的道長(zhǎng)涕侈。 經(jīng)常有香客問(wèn)我,道長(zhǎng)煤辨,這世上最難降的妖魔是什么裳涛? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮众辨,結(jié)果婚禮上端三,老公的妹妹穿的比我還像新娘。我一直安慰自己泻轰,他們只是感情好技肩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布且轨。 她就那樣靜靜地躺著浮声,像睡著了一般。 火紅的嫁衣襯著肌膚如雪旋奢。 梳的紋絲不亂的頭發(fā)上泳挥,一...
    開(kāi)封第一講書(shū)人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音至朗,去河邊找鬼屉符。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的矗钟。 我是一名探鬼主播唆香,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吨艇!你這毒婦竟也來(lái)了躬它?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤东涡,失蹤者是張志新(化名)和其女友劉穎冯吓,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體疮跑,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡组贺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祖娘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片失尖。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贿条,靈堂內(nèi)的尸體忽然破棺而出雹仿,到底是詐尸還是另有隱情,我是刑警寧澤整以,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布胧辽,位于F島的核電站,受9級(jí)特大地震影響公黑,放射性物質(zhì)發(fā)生泄漏邑商。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一凡蚜、第九天 我趴在偏房一處隱蔽的房頂上張望人断。 院中可真熱鬧,春花似錦朝蜘、人聲如沸恶迈。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)暇仲。三九已至,卻和暖如春副渴,著一層夾襖步出監(jiān)牢的瞬間奈附,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工煮剧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斥滤,地道東北人将鸵。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像佑颇,于是被迫代替她去往敵國(guó)和親顶掉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容