函數式編程(Functional Programming)相比面向對象編程(Object-oriented Programming)有哪些優缺點?

蔣經堯


面向對象和函數式編程主要是抽象方式的區別,抽象方式的不同導致了優缺點的不同。

抽象方式

面向對象將現實抽象為一個個的對象,以及對象間的通信來解決問題。

函數式編程將現實抽象為有限的數據結構,以及一個個的對這些數據結構進行操作的函數,通過函數對數據結構的操作,以及函數間的調用來解決問題。

對象也是一種數據結構,面向對象實際是將現實抽象為了無數個具有有限操作的數據結構。

反過來函數式編程是將現實抽象為具有無數個操作的有限個數據結構。

兩者的優缺點就很明顯,面向對象可以很方便的增加類型,卻很難在不修改已定義代碼的前提下,為既有具體類實現一套既有的抽象方法(稱為表達式問題)。而函數式編程可以很方便的增加操作,但很難新增一個適應各種既有操作的類型。

比如Java裡的字符串本身沒有capitalize方法,理想的方法應該是“abc”.capitalize(),但是Java裡需要Util類來實現。雖然ruby可以通過猴子補丁實現,scala可以通過隱式轉換實現,kotlin可以通過intern方法實現,但是實際上是通過各種手段彌補這個缺陷。

對於函數式語言來說,比如jvm上的lisp語言Clojure,它的集合都實現了n多個接口:Collection,Sequence,Associative,Indexed,Stack,Set,Sorted。使得一組函數可以應用到set,list,vector,map。如果要新增一個數據結構,就需要實現n多的方法。

不可變性

另外一個抽象方式的區別就是不可變性和可變性。這個很多地方有講,就不細聊了。

函數式編程強調不可變性,所以易於支持併發編程,但對於狀態的修改就需要特殊處理。面向對象編程則反之,狀態想怎麼變就怎麼變,所以併發編程需要加各種鎖。

抽象程度

抽象方式的不同,間接導致了抽象程度的不同。

函數式編程的抽象程度要高於面向對象,因為最終都是抽象為那幾個數據結構。而面向對象可以構建貼近現實的數據對象。

抽象度越高,也就越難理解。但是抽象度越高,適應性就越強,代碼相對就越簡潔。

抽象粒度

抽象程度高,也就間接導致抽象粒度細。抽象粒度細,控制性更好,但也導致了相對較難維護。

比如面向對象的23種設計模式,對於函數式編程來說,就是一句話的事。

以策略模式來說,Java的實現如下:

public class Context { private Strategy strategy; public Context(Strategy strategy){ this.strategy = strategy; } public void contextInterface(){ strategy.strategyInterface(); } }
public interface Strategy { public void strategyInterface(); }
public class ConcreteStrategyA implements Strategy { @Override public void strategyInterface() { //相關的業務 } } public class ConcreteStrategyB implements Strategy { @Override public void strategyInterface() { //相關的業務 } }

而對於Clojure來說,直接使用高階函數就可以了:

(defn context [f] (f))(defn strategy1 [] (println "Strategy1")) (defn strategy2 [] (println "Strategy2")) (context strategy1)(context strategy2)

維護問題,可以把類看作一本書的章節,函數看作書的小節。一本書20個章節就算很多了,而小節至少有個大幾十個,數量越多,越難維護。且因為抽象度高,函數如何放置的問題也需要花時間好好考慮。


架構思維


為什麼函數式編程正在上升

編程範例是一個術語,用於描述編寫命令的方法。 語言的真正思想是建立在其編程範例之上的。 最著名的三種範例是面向對象的程序設計,命令式程序設計和函數式程序設計。 任何一種意識形態都不會比另一種更好,因為通常它是關於使用正確的工具完成工作的。

函數式編程是大多數軟件工程師至少對此不太熟悉的概念。 實際上,有史以來寫給程序計算機的第二種編程語言Lisp完全在功能範式內。 通過簡化的定義編程,函數式編程將數據的不變性和數學計算作為優先事項,而不是傳統上修改存儲在類構造函數中的部分對象。 在現代函數式編程中,這個想法有些冗長,不一定是一件壞事。 函數語言的可變性帶來了更多實用性,我敢說:

功能性

功能編程。 考慮到這一點,函數式編程不限於函數式語言。 例如,Python具有功能特性。 儘管從傳統上講,函數式編程已經有了令人難以置信的不同,但是函數式和麵向對象的範例似乎與大多數用於數據科學的語言有點相似。

我們做的偉大的事情

大多數功能語言的標題均帶有"統計"字樣。 這很方便,因為數據科學家非常像統計學家,只需要具備編程和機器學習技能即可。 函數式語言通常可以更快,而對於數據科學家而言,最重要的是更容易。

相信我,您不想瀏覽C代碼,因為您的準確性有點低。 大多數功能語言都是完全可讀的,並且很容易鍵入和掌握。 對於某些人來說,知道函數式編程是許多Internet上最古老的大數據管道的基礎,可能會令人感到意外。 實際上,隨著機器學習和統計計算的興起,函數式編程實際上變得越來越流行。

炫酷的編程語言

有許多很酷的統計語言,其中許多語言具有與其他更傳統的特徵交織在一起。 在函數式編程語言下,我非常喜歡很多很酷的語言。

Julia

Julia很容易成為我一直以來最喜歡的語言。 儘管Julia確實是功能性的,但它確實包含一些可變的和麵向對象的屬性,使其編程起來更加方便。Julia作為這樣一種高級語言的速度非常快。 它易於鍵入,鍵入正確時,它可以與C一樣快,同時比R和Scala易於閱讀。 通常,不需要花費太多時間就可以在Julia中建立模型並對其進行訓練,並且由於語言簡單易行且ML速度快,這增加了使用Julia的好處。 在某些情況下,例如在我的ML包Lathe中,機器學習可以用更少的行完成,並且構造函數的屬性可以使用參數多態性更輕鬆地進行突變。

Lisp

儘管Lisp不一定以其數據科學領域而聞名,但它仍然是一種非常酷的語言。 Lisp和Julia一樣,使編程變得非常容易和方便。 Lisp的宏和Julia的宏是一個真正值得注意的屬性。 宏本身是一項主要的功能性功能,如果正確使用,可以使筆記本編碼真正容易。 應該注意的是,Lisp已將自己分為多種語言,包括Scheme,Clojure和(通用)Lisp。 很難理解或估計第二種高級語言及其功能的影響,但是範圍肯定很大。

R

現在,我們迎接我們的好朋友R. R傳統上是一種面向函數的語言,但是像大多數其他語言一樣(或已經成為)多範式,這意味著它從每個特定的編程範式中進行選擇。 很好,因為它使R具有可變性的優點。 R起源於S語言,並且一直專注於統計計算。

Haskell

Haskell與我上面提到的多種範式語言完全不同,Haskell以純粹的功能而自豪。 我無法代表Haskell,與清單上的其他語言不同,我從未使用過。 但是據我所知,Haskell當然是一種很酷的語言。 我對學習Haskell的關注純粹是侷限性。 正如我所討論的那樣,大多數現代語言都是多範式的,這使它們可以有效地壓縮需要壓縮的任何錯誤,而無需創建新的代碼庫。

最後的想法

我的大部分時間都花在函數式語言(主要是Julia)上,如Github上的筆記本資料庫所顯示的那樣,其中主要包含Julia筆記本。 我喜歡函數式編程,因為對於我所做的事情,它非常適合該規則。 當然,有時候Python構造函數(類)可能對某個特定的工作會更好,但是總的來說,Julia可以完成工作,而且鍵入的方式對我來說非常流暢和高效。 對於其他人而言,功能語言可能無法實現其目標,並且對於一生都使用面嚮對象語言的人來說可能很難學習。 歸根結底,語言是一種選擇,大多數語言都有開發人員,大多數語言都有優缺點。


聞數起舞


面向對象編程(OOP),是命令式編程的類型系統發展到一定階段的必然產物。而 命令式編程,背後的數學原理是 圖靈機。

圖靈機→命令式編程→OOP

函數式編程(FP)則直接從 數學中的 λ演算 發展而來,其類型系統繼續發展就是 面相範疇編程。

λ演算→FP→面相範疇編程

早在上個世紀,計算機出現之前,圖靈就在數學上證明了 圖靈機 和 λ演算 的等價性。這在理論上,說明,OOP 能實現的 FP 都能實現,反之亦然。同時,還說明 OOP語言 也可以提供 FP 的機制,反之亦然。事實上,現在的通用語言都是這麼幹的,例如:C#、Java、C/C++、Python 等 都提供有 λ表達式;R、Commonlisp、JavaScript、Ocaml、Haskell 等 都支持OOP。

OOP的優點是符合人類看待世界的方式:類-對象-屬性(方法),非常適合在程序架構上使用,結合設計模式,幾乎可用於各種程序架構當中。缺點是不夠靈活不適合算法,用於實現事件響應的代碼笨重 等。FP剛好相反,優點是靈活輕巧,適合算法、事件代碼。缺點是以函數為中心的思維方式太過數學化,並且使得程序的架構龐雜,不易維護。所以,可見,最好的辦法是OOP和FP配合使用。


思考思考的動物


面向對象特別符合人類對世界認知的思維模式。oop的興起源於,大型軟件系統的開發對代碼管理、複用、擴展的一種需要。oop為代碼結構的合理性,提供了更多設計的可能和空間。oop的三大特徵,封裝,繼承,多態其實都是表面特徵,oop真正的靈魂是抽象,這個概念貫穿在oop語言的各個層面和開發的各個環節,所以能深刻理解抽象,才能深刻的認識oop,才能把oop的優勢發揮出來。

函數式編程是個很古老的東西,它之所以今天能再被重用。是因為數據量的激增和硬件高速發展帶來的多核處理需求。函數式最大的特點是無狀態,它沒有變量,因此它天生線程安全,這決定了它特別適合高併發,大數據量的處理。

面相對象和函數式,都是從語言特性層面得到支持的一種編程思想,他們之間並不矛盾,並不是兩種對立的思想。因此,完全可以取他們的優勢,來進行開發,這也是未來開發語言發展的一個方向,就是多範式編程,也就是同時支持面相對象和函數式,現在一個最典型的代表就是scala。


geyall


函數式編程代表語言lisp, 編出的程序比較短,適合簡短的程序。數據一般是列表方式。函數式編程只適合函數變量比較簡單的情況。

面向對象編程更容易理解識別變量,適合大規模編程。數據一般是數據庫。面向對象編程變量不容易混淆,也允許變量缺省,適合編遊戲。面向對象編程,變量,函數是封裝的。所以不容易被幹擾。適合更復雜的情況。

函數是先出現的概念,更容易理解一些。函數式編程有點像第一人稱的文章。對象編程是第三人稱寫的小說。它有人的名字,動作可以是類似的。

可以先從函數編程學起。過渡到對象編程。更自然一些。


蘋果188312167


各有各的優缺點,並不是哪一種是最好的。


分享到:


相關文章: