函數式編程初窺

   最近在學習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方法負責排序,須要把比較的規則告訴他,將比較方法做爲一個參數傳到函數中進行運算。

 

 

—三、函數式編程技術(方法論)

 

  • —映射化簡(map & reduce)函數式編程最多見的技術就是對一個集合作Map和Reduce操做。這比起過程式的語言來講,在代碼上要更容易閱讀。傳統過程式的語言須要使用for/while循環,而後在各類變量中把數據倒過來倒過去的
—
  • 管道    (pipeline)把一組函數放到一個數組或是列表中,而後把數據傳給這個列表,數據就像一個pipeline同樣順序地被各個函數所操做,最終獲得咱們想要的結果。他的設計哲學就是讓每一個功能就作一件事,並把這件事作到極致,軟件或程序的拼裝會變得更爲簡單和直觀。
 
  • —遞歸    (recursing)遞歸最大的好處就簡化代碼,他能夠把一個複雜的問題用很簡單的代碼描述出來。遞歸的精髓是描述問題,而這正是函數式編程的精髓。
 
  • —柯里化  (currying)把一個函數的多個參數分解成多個函數, 而後把函數多層封裝起來,每層函數都返回一個函數去接收下一個參數。
 
  • —高階函數(higher order function)把函數當參數,接受一個函數做爲參數的函數就叫高階函數。現象上就是函數傳進傳出。

 

  map & reduce

  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語言早就證實了這一點,它是瑞典愛立信公司爲了管理電話系統而開發的,電話系統的升級固然是不能停機的。

 

最後,其實使用面向對象或者面向方法都不重要,重要的是如何理解其中的價值觀和方法論,構造可維護、可擴展、穩定又靈活的程序。無論白貓黑貓抓到老鼠就是好貓。

 

  參考:

相關文章
相關標籤/搜索