為什么我們需要知道“函數式編程”?

說在前面

注意,本文所討論的函數式編程,并不等同于函數式編程“語言”,而是這么一個思想和概念,相信看到最后你或許能夠明白這句話。

 

問題

首先是關于計算機領域需要知道的一些事情,那就是硬件。

由于硬件發展已經快要到達物理極限了,也就是說摩爾定律已經慢慢開始失效,由于我并不是硬件相關的專家,所以也無法確定這是不是真的,但我們假設這就是真的。

摩爾定律失效過后會帶來什么影響呢?那就是我們編寫的程序再也無法像以前那樣,只要等上18月還是多久(這個不是重點),就可以讓我們的處理能力或是性能提升一倍。

所以,當硬件的發展慢慢放緩,而我們業務規模增長超過單機極限后,唯一的辦法就是將程序分布到不同的計算機上面運行,通過多個計算機來水平提升我們的處理能力。

 

狀態的一致性

當我們把程序放到多個計算機上運行,形成集群,其中多個節點如果同時訪問、修改同一個狀態,就會造成數據一致性問題,這也是分布式程序為什么這么難寫的原因之一。

同樣的事情其實發生在我們在編寫多線程應用程序的時候,當我們在使用命令式或者面向對象語言的時候,我們可以直接對狀態進行修改,比如有一個變量,我們隨時可以給他賦值。

當然如果在任意時刻,只有一個線程能夠訪問和修改這個變量,這就沒有問題。

但是別忘了其他線程也能夠訪問這個變量,當多個線程同時讀取、修改變量的時候,如果沒有任何的保護措施(比如鎖),那么就有可能會出現狀態被錯誤的讀取和計算,甚至是被覆蓋。

所以:

  • 多線程訪問的是變量。
  • 分布式集群訪問的是數據庫或者緩存。

有什么區別呢?除了存放的位置不一樣,基本上遇到的問題是一樣的,那就是必須要協調和控制好,并發所帶來的狀態一致性問題(無論是變量、還是數據庫、緩存,我統稱它們為狀態)。

我們的程序擁有對狀態的完全控制權,在任何時候任何地方都能夠修改狀態,可以想象,如果我們沒有對狀態進行有效的管理的話,就很容易造成混亂,維護性大大降低。

其實就跟咱們一開始學編程的時候喜歡使用全局變量一樣,但是現在的問題更棘手。

如果說全局變量的影響是平面的,我們只需要線性的去梳理修改這些狀態代碼的先后順序就能夠解決BUG的話。

那么加上并發競爭,這個影響就是3D的,排查BUG以及組織程序的復雜程度整整被提高了一個維度,因為空間與時間不再是一一對應的關系了。

我之前寫的好幾篇文章,幾乎都是跟分布式以及一致性相關的主題,現在本文又提到了這些問題,很多重復的內容我就不贅述了,有興趣的可以翻翻我之前的文章。

關于硬件,我留下一個問題給有興趣的小伙伴,那就是為什么顯卡的計算能力大大超過了CPU?

 

函數式編程當中的純函數

難道就沒有一個行之有效的辦法來解決這些令人棘手的問題嗎?

如果你經常閱讀博客或者關注最新的技術文章和框架的話,你會發現,很多框架都開始慢慢支持以及完善從“同步”發展到“異步”的這個過程中了。

什么是同步?

我們每天正在編寫的代碼是符合人腦思維順序的,從上到下依次執行,比如x = 2,然后x = x * x,很容易就得出最后x == 4的結論。

其中x就是一個變量,可能儲存在CPU的寄存器當中,也可能儲存在堆內存中,但這不是重點,它們都在同一個計算機上。

同步就是依次執行,按照我們所編寫代碼的順序逐步執行完所有的代碼,我們利用分支判斷以及循環語句來控制執行的線路,依賴的是之前被計算好的狀態變量。

什么是異步?

同步的缺陷很明顯,由于是嚴格按照先后順序執行代碼的,這也是我們預期的方式。

但是一旦涉及到IO操作,比如文件、網絡,整個程序的運行就會被阻塞,因此多線程可以幫助我們,將阻塞的操作與當前的流程分離開來,等讀取完后再去執行相關的操作。

我們可以通過回調或者事件的方式來異步的進行處理,所謂異步,簡單粗暴的理解就是我們調用了一個函數,他不會立馬得到結果,而同步就可以得到結果,哪怕時間再長,我們也等(阻塞)。

可以想象異步增加了代碼的復雜程度,因為本來同步是直接返回結果的,異步就需要我們在另一個處理單元等待喚醒然后繼續操作。

另外,同步的代碼只能在一個CPU當中執行,而多線程異步則可以利用計算機上面其他的CPU,使其并行執行提高效率。

 

想象一下如果我們有一個函數,它不會修改任何狀態,僅僅是對參數進行計算,然后返回計算結果,然后我們將這個函數分布到不同的計算機上去執行,是不是就能不受制于單臺計算機天花板的影響了呢?

由于這個函數不會修改任何狀態,不會有任何的副作用,所以它可以在任何地方執行而不需要依賴其他的條件,這種函數被稱之為純函數。

純函數就像是一個可以被隨時移動到不同地方去執行的單元。

因此,純函數就可以被當做一個異步等待喚醒的處理器,我們不知道它會在什么時候被執行,但我們可以放心,因為它不會導致副作用,也不需要依賴其他的前置條件。

你或許注意到了本文所講的內容其實就是Actor模型,沒錯,你可以認為每一個Actor就是一個個純函數。

但無論如何,你是自由的,你可以在actor執行單元里面做任何事情,但是請記住純函數不得引發任何的外部狀態修改,這是原則也是根本,因為我們不想要副作用。

所以在純函數式編程“語言”里面,根本就沒有賦值操作,不過是描述對輸入進行處理,然后返回結果而已,這能夠讓我們少犯一些錯誤。

現在,處理器我們有了(純函數),它可以被當做異步處理單元在任何計算機上面執行,那么狀態呢?

 

命令式vs聲明式

注意我并不會介紹函數式編程的所有特性,有關這方面的資料我相信已經存在了。

什么是命令式?什么又是聲明式?

舉個例子,還記得你為什么用Spring嗎?最基本的就是因為你需要依賴注入。

我只需要聲明一個Bean他需要依賴哪些類型的實例,Spring會為我們找到并且傳入,這就是聲明式,而如果你自己進行實例化操作,那么你需要去找到這些依賴,這就是命令式。

Don’t call us, we’ll call you.

 

由于純函數不會修改狀態,他只是簡單的輸入(參數)-> 計算 -> 輸出(返回)IO單元,因此也就沒有了賦值操作。

由于純函數不會修改狀態,他只是簡單的輸入(參數)-> 計算 -> 輸出(返回)IO單元,因此也就不需要賦值操作(對外部狀態進行修改)。

如果說傳統編程是對狀態進行操作(修改),那么函數式編程語言就恰好相反,它不會修改狀態,它只是描述計算的過程。

因此,傳統的編程對狀態的修改是命令式的,函數式編程對狀態的修改則是描述聲明式的。

如果無法理解這句話,想想Java8里面的Stream API以及Lambda表達式吧,分解過后的狀態轉換、過濾、收集以聲明的方式進行調用,更加直觀方便,而且可以并行執行而無需修改其余代碼。

實現函數式編程的具體語言、虛擬機執行環境以及框架,將會負責狀態的維護,以及編排分布你的純函數,在某一個地方某一個時間被激活執行。

不要認為我所講的只是函數式編程“語言”,實際上只要遵循相關的規則,使用框架也是一樣的,重要的是這些規則概念背后所代表的意義。

就像那句話怎么說來著?

就算是使用C語言,我們也能夠進行面向對象編程。但如果不懂面向對象,就算使用Java、C++,那也跟使用C語言沒有區別。

或許我們會使用這些高級語言的功能特性,但是卻無法理解為什么需要這些特性,我們只是按照語言的規范來實現我們的需求。

本末倒置的一個后果就是,我們依賴特定的編程語言而非依賴我們的思維以及經驗。

Erlang、Lisp、Scala、Akka、Vert.x、Reactor、RxJava等等等等,不管是語言、框架或者工具庫,都能幫助我們減少進行異步編程所要的工作量,但你或許會問:這有什么用呢?為什么我們需要異步呢?

答案就是,我們需要開發分布式應用程序,它們能夠在不同的計算機上面運行,形成集群以提供大規模的并發應用服務,但同時,我們也需要完全利用好每一臺計算機上的資源。

因此,我們需要將資源的控制交給這些框架,交給它們背后所支撐的理論與實踐,這樣我們就能夠站在巨人的肩膀上。

 

但函數式編程不是銀彈

因為跨網絡的優化以及狀態的管理,因此跟業務場景相關的偏好設定仍然無法被忽略。

我們需要根據我們自己的情況來進行組織,只有這樣才能最大化的避免由于當前計算機體系架構所固有的缺陷而引發的問題,以最小的代價換取利用資源。

函數式編程能夠讓我們放棄一些權力,來換取規則下的和平,但它終究不過是一種工具,如果我們能夠在適當的場景利用好這個工具,就能夠使我們的工作更加有效。

采用任何技術都無法脫離對原始業務的洞悉,只有這樣,我們才能夠構建出最佳匹配的應用程序服務。

脫離應用場景的使用,不僅會使得后期維護成本上升,還會使得架構的演化遭遇巨大的挑戰,除非你真的明白在做什么,否則我們可能永遠也無法得到有效的改善。

 

最后

如果我們遵循函數式編程的一些規范約束,就能夠減少一些錯誤,因為正是由于這些規范約束的存在,才使得我們避免陷入泥潭而無法自拔。

越來越多的框架都開始支持以及完善異步編程,就像Spring 5所推出的WebFlux,使用Reactor(https://projectreactor.io)作為基礎支撐,帶來的就是全異步化的聲明式編程范式。

再例如Vert.X(https://vertx.io)這個讓人用的上癮的強大工具,當你瀏覽了越來越多的新開源項目,或者是已經存在的開源項目開始慢慢過渡的轉變趨勢。

你會發現,很多知識概念都是通用且可以互相轉換的,異步、分布式、非阻塞、事件驅動、反應式等等…

而這些就是面向未來的編程知識,無論使用何種語言、框架或者工具庫。

 

posted @ 2019-10-11 10:00  XingxueLiao  閱讀(...)  評論(...編輯  收藏
七乐彩2011年走势图南方双彩