最近在學習Erlang和Python。Erlang是徹底的函數式編程語言,Python語言是面向對象的語言,可是它的語法引入了大量的函數式編程思想。越研究越以爲函數式的編程思路能夠幫助咱們規避不少Bug,因此在這裏對函數式編程作一個簡要的介紹。分析函數式編程的特色、方法論,使用的技術,以及同面向對象編程的異同。html
函數式編程誕生於50多年前。如今愈來愈多的人開始接受並進行函數式編程的實踐。不只最古老的函數式語言 Lisp 重獲青春,並且新的函數式語言層出不窮,好比 Erlang、clojure、Scala、F#等等。目前最當紅的Objective-C, Python、Ruby、 Javascript都引入了對函數式編程的支持。就連老牌的面向對象的 Java、面向過程的 PHP, 以及蘋果最新的swift語言,都忙不迭地加入匿名函數等機制。python
愈來愈多的跡象代表,函數式編程已經再也不是學術界的最愛,開始大踏步地在業界投入實用。 也許繼面向對象編程以後,函數式編程會成爲下一個編程的主流範式。git
說到函數式編程編程就不得不說面向對象。面向對象是把一個功能的一組操做和相關數據封裝在一個對象裏,面向對象是對象滿天飛。函數式編程是把一個功能的一個操做和相關數據封裝在一塊兒,函數式編程是函數滿天飛。函數式編程比面向對象的優點就是粒度更小,生命週期更短。減小bug的有效途徑就是減小變量的生命週期,縮小模塊的粒度;因此函數式編程更不容易引入bug。github
是一種編程範型,它將計算機運算視爲數學上的函數計算,而且避免使用程序狀態以及易變對象。函數編程語言最重要的基礎是λ演算(lambda calculus)。λ演算中最關鍵的要素就是函數被看成變量處理,可以參與運算。shell
下面是一個輸出數組的例子 編程
shoplist = ['apple','mango','carrot','banana'] print 'My shopping list is now', shoplist #輸出 #My shopping list is now ['banana', 'carrot', 'mango', 'rice']
這樣的代碼更易讀,代碼只是描述在幹什麼,而不是如何作到這點的具體實現。若是是過程式編程,須要一個for循環去描述實現細節。swift
函數式編程在解決複雜運算問題時,把一個問題分解爲若干子問題,逐步求解。軟件或程序的拼裝會變得更爲簡單和直觀。使代碼更容易理解,方便排查問題,而且具備更好的可維護性和擴展性。數組
如今有這樣一個數學表達式:併發
(1 + 2) * 3 - 4app
傳統的過程式編程:
var a = 1 + 2;
var b = a * 3;
var c = b - 4;
函數式編程要求使用函數,咱們能夠把運算過程定義爲不一樣的函數,而後寫成下面這樣:
var result = subtract(multiply(add(1,2), 3), 4);
你的優秀和個人人生無關,請帶着你的趾高氣揚滾蛋吧。這個就是函數式編程的準則:函數不受外部變量影響,不依賴於外部變量,也不改變外部變量的值。
傳統的過程式編程:
int count; void increment() { returen count++; }
函數式編程:
def increment(count): return count+1;
函數不訪問全局變量,也不改變全局變量。
封裝、繼承、多態是面向對象編程的三大特性。函數式編程也有本身的語言特性。
數據不可變性(immutable data)多有的變量只能夠賦值一次,變量不可變,若是想改變變量就建立一個新的變量。
數據的不可變性保證了程序是無狀態的,不少難解的bug每每是由各類複雜的狀態引發的。好比發現某些狀況下程序運行有問題,是某一個狀態引發的,可是這個狀態有100種可能性,在1000個地方都有對這個狀態進行操做。debug的時候要殺人的心都有了。數據不可變性同時保證了函數沒有「反作用」,函數的反作用是指除了返回函數值外,還對主調用函數
函數是第一公民(first class method)函數能夠像普通變量同樣去使用。函數能夠像變量同樣被建立,修改,並當成變量同樣傳遞,返回或是在函數中嵌套函數。
引用透明(referential transparency) 指的是函數的運行不依賴於外部變量或「狀態」,只依賴於輸入的參數,任什麼時候候只要參數相同,調用函數所獲得的返回值老是相同的。自然適應併發編程,由於調用函數的結果具備一致性,因此根本不須要加鎖,也就不存在死鎖的問題。
尾遞歸化(tail call optimization)由於函數調用要壓棧保存現場,遞歸層次過深的話,壓棧過多會產生性能問題。因此引入尾遞歸優化,每次遞歸時都會重用棧,提高性能。
把函數做爲參數傳遞的例子
NSComparisonResult (^cmp)(id obj1, id obj2) = ^NSComparisonResult(id obj1, id obj2) { return [obj1 isEqualToString:obj2]; } NSArray* items = [@"a", @"c", @"d"]; [items sortedArrayUsingComparator:cmp];
sortedArrayUsingComparator方法負責排序,須要把比較的規則告訴他,將比較方法做爲一個參數傳到函數中進行運算。
Python代碼:
def toUpper(item) return item.upper() print map(tuUpper , [「hellow」,」world」])
將數組裏的全部字符串變爲大寫,直接使用map,不須要寫for循環。最後輸出 ["HELLO","WORLD"]
print reduce(lambda x , y : x + y,[1,2,3,4,5])
將數組裏全部數值進行累加,至關於1+2+3+4+5,輸出 15。
lambda是Python的匿名函數,lambda x,y:x+y至關於def func(x,y):return x+y
若是有一個需求找出一組數中的全部偶數並對他們求平方,最後求他們的和,能夠分解成三個步驟:
1)找偶數
2)求平方
3)累加
def even(nums): return filter(lambda x: x%2==0, nums) def square(nums): return map(lambda x: x*x, nums) def total(nums): return reduce(lambda x,y:x+y,nums) nums = [1,2,3,4,5,6,7,8,9,10] pipeline = total(square(even(nums)))
even方法求偶數,square求他們的平方,total方法將他們加在一塊兒。經過管道的方式把他們串聯在一塊兒,一個複雜的處理就完成了。
讓每一個方法只作一件事,並把這件事作到極致。
在其餘類型的語言中,變量每每用來保存狀態。變量不可變,意味着狀態不能保存在變量中。函數式編程使用參數保存狀態,最好的例子就是遞歸。
void fun(const int i) { if (i < 10 && i >= 0) { NSLog(@"i:%d", i); fun(i + 1); } }
每次狀態的變化就是值+1。
柯里化就是一個函數只有一個參數,那若是須要兩個參數怎麼辦,好比兩個數相加求和。經過把一個參數封裝成函數的方式實現。
def func(a): def add(b): return a+b return add funcA = func(5) print funcA(10)
func函數返回一個add函數,funcA變量就是一個a爲5的add函數。print funcA(10)就是向add函數傳入10,最後結果就是5+10 輸出15。
函數式編程大量使用函數,減小了代碼的重複,所以程序比較短,開發速度較快。
函數式編程注重幹什麼而不是怎麼幹,更容易理解。
函數式編程不依賴、也不會改變外界的狀態,只要給定輸入參數,返回的結果一定相同。所以,每個函數均可以被看作獨立單元,頗有利於進行單元測試 (unit testing)和除錯(debugging),以及模塊化組合。
函數式編程由於它不修改變量,因此根本不存在"鎖"線程的問題,不須要考慮"死鎖"(deadlock)。沒必要擔憂一個線程的數據,被另外一個線程修改,因此能夠很放心地把工做分攤到多個線程中。
函數式編程沒有反作用,只要保證接口不變,內部實現是外部無關的。因此,能夠在運行狀態下直接升級代碼,不須要重啓,也不須要停機。Erlang語言早就證實了這一點,它是瑞典愛立信公司爲了管理電話系統而開發的,電話系統的升級固然是不能停機的。
最後,其實使用面向對象或者面向方法都不重要,重要的是如何理解其中的價值觀和方法論,構造可維護、可擴展、穩定又靈活的程序。無論白貓黑貓抓到老鼠就是好貓。