函數式編程是一種編程范式,和面向對象的編程方式一樣,是一種編程思想。函數式編程現在相當的火爆,最近也在關注這方面的思想。
對於面向對象編程主要有三點特性:封裝,多態,繼承
封裝就是把對象的屬性和對象的行為封裝到一個定義的類里面
多態就是同一個對象可以表現為多種具體的形式
繼承就是子類可以繼承父類的屬性和行為
面向對象編程的思想是把所有的事物都當做對象來看待,任何事物皆對象。我們在學習面向對象的編程的時候,最喜歡舉的例子就是形狀->長方形->正方形 ,對於這種例子,面向對象的編程的確是非常合適的。對於大多數的自然界事物,都是可以抽象出來一個具體的對象,在具體化對象的屬性和行為。這種編程思想和人類的認知具體事物的方式非常接近,所以面向對象的方法一直流行了20幾年。
但是在某些情況下,並不是世界上所有的事物都適合用對象來表示,最常見的就是數學中的科學計算和邏輯運算
我們舉一個簡單的例子,我們定義一個函數 f(x) = x*x + 2*x - 100 ,我們需要對f(1)到f(n)的結果進行求和,看看如何實現這一段邏輯
用面向對象的方式實現
面向對象的方式需要抽象出來一個對象來表現現實世界的事物,在這里,我們定義個函數接口,來表現f(x)這個函數,通過實現這個接口,來實現具體的算法.
下面是function類的定義
public interface Function { public int call(int x); }
在來定義具體的實現函數
public class ConcreteFunction implements Function { @Override public int call(int x) { return (x * x + 2 * x + 1); } }
最后我們定義一個靜態類,我們發現這個類基本上沒有任何意義,只是為了進行一次計算行為,當然我們還是可以抽象出來這一次計算行為,但是在這個例子里面,我們簡單一點,就是定義了一個靜態類實現這個功能
public class SumUtils { public static final int sum(int n) { int temp = 0; for (int i = 0; i < n; i++) { temp = temp + new ConcreteFunction().call(i); } return temp; } public static void main(String[] args) { System.out.println(SumUtils.sum(10)); } }
最后打印出來結果是 385.
用面向對象構造出來這個過程,有兩個地方比較丑陋,對於不同的f(x)我們需要定義出具體的實現類,每一個實現類代表一個具體的算法,用面向對象的觀點來看,不同的邏輯函數是不同的對象,這些對象之間完全沒有任何聯系,而且這些對象沒有任何屬性,不存在封裝的必要,同時也不會有被繼承的特性。在計算我們想要的結果的時候,我們必須在定義無意義的對象去找出我們想要的結果,這純粹是為了面向對象而定義對象,最后一個SumUtils類完全沒有任何意義。
從上面這個例子來看,面向對象並不是任何時候都適用的。下面我們看看用函數式編程如何實現這個功能。這里我們選擇scala語言來實現
def main(args: Array[String]) = { var f = { x: Int => x * x + 2 * x + 1 }; var result = 0; for (val x <- 0 to 9) { result = result + f(x); } println(result); }
我們首先定義了一個f 函數,可以采用def f(x:Int) = {x*x + 2*x + 1} 的方式,也是采用代碼塊的方式,然后進行循環調用,計算出來結果。
從上面的代碼行數來看,四行代碼可以搞定。如果用一些高階功能,代碼可以做到更大皆簡化和最大的擴展性。極端情況下一行代碼就可以搞定。用函數來表示數學中的函數,比用對象表現數學中的函數更形象,更具有可讀性。
函數式編程的特點:函數是第一公民,無副作用,內部不存在狀態,易於並發。
有時候在想,面向對象和函數式編程結合起來,能夠極大的簡化我們的編程工作,寫出跟易於擴展的和可讀的代碼。在設計上,兩者思想是想通的。在不同的領域里面,用不同的思想去設計程序,這個是需要我們分析的。
其實面向對象程序中的設計模式,很多和函數式編程里面的思想想通。我們常用的模板模式和回調模式和函數式編程里面的高階函數設計思想類似,當設計能力達到一定層次之后,在去看函數式編程,就會對設計模式的理解更家深入。
正好這兩天也看到一個詞,編程極端主義——大概說的是,當某了解某一概念的時候,在任何情況下都套用這一概念。
編程極端主義 (跟極限編程沒有關系)是一種接受某種理論、在所有事情上檢驗它、在所有地方運用它的行為。一通實驗,塵埃落定后,人們通常會回想這次極端行為,認識到“不錯,這很有趣,但很明顯,在Y上使用X明顯不合適。干這個事情我們需要使用合適的技術!”
編程極端主義能夠讓你在學習一門新的技術或者概念的時候更深入的了解其特性,適用性以及局限性。基本上,java語言的設計思想就是一個編程極端主義的典型用例,在java里面,基本上任何東西都是對象(除了基本的類型之外),在學習了java之后,對面向對象的方式編程會有非常深入的了解,但是思想也會被局限在其中,導致在任何時候都會用面向對象的方式去思考,而這其中有一些並不適合面向對象的設計,最典型的就是java里面的Math類。
在學習函數式編程,也可以按照這個思路去學習,忘掉面向對象的設計,在任何情況下考慮函數式思想。haskell就是一門純粹的函數式編程。