1. 程式人生 > >為什麼我們需要知道“函數語言程式設計”?

為什麼我們需要知道“函數語言程式設計”?

說在前面

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

 

問題

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

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

摩爾定律失效過後會帶來什麼影響呢?那就是我們編寫的程式再也無法像以前那樣,只要等上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單元,因此也就沒有了賦值操作。

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

因此,傳統的程式設計對狀態的修改是命令式的,函數語言程式設計對狀態的修改則是描述宣告式的。

如果無法理解這句話,想想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)這個讓人用的上癮的強大工具,當你瀏覽了越來越多的新開源專案,或者是已經存在的開源專案開始慢慢過渡的轉變趨勢。

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

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

&n