注意,本文所討論的函數式編程,並不等同於函數式編程「語言」,而是這麼一個思想和概念,相信看到最後你或許可以明白這句話。react
首先是關於計算機領域須要知道的一些事情,那就是硬件。數據庫
因爲硬件發展已經快要到達物理極限了,也就是說摩爾定律已經慢慢開始失效,因爲我並非硬件相關的專家,因此也沒法肯定這是否是真的,但咱們假設這就是真的。編程
摩爾定律失效事後會帶來什麼影響呢?那就是咱們編寫的程序再也沒法像之前那樣,只要等上18月仍是多久(這個不是重點),就可讓咱們的處理能力或是性能提高一倍。緩存
因此,當硬件的發展慢慢放緩,而咱們業務規模增加超過單機極限後,惟一的辦法就是將程序分佈到不一樣的計算機上面運行,經過多個計算機來水平提高咱們的處理能力。網絡
當咱們把程序放到多個計算機上運行,造成集羣,其中多個節點若是同時訪問、修改同一個狀態,就會形成數據一致性問題,這也是分佈式程序爲何這麼難寫的緣由之一。多線程
一樣的事情其實發生在咱們在編寫多線程應用程序的時候,當咱們在使用命令式或者面嚮對象語言的時候,咱們能夠直接對狀態進行修改,好比有一個變量,咱們隨時能夠給他賦值。架構
固然若是在任意時刻,只有一個線程可以訪問和修改這個變量,這就沒有問題。併發
可是別忘了其餘線程也可以訪問這個變量,當多個線程同時讀取、修改變量的時候,若是沒有任何的保護措施(好比鎖),那麼就有可能會出現狀態被錯誤的讀取和計算,甚至是被覆蓋。框架
因此:異步
有什麼區別呢?除了存放的位置不同,基本上遇到的問題是同樣的,那就是必需要協調和控制好,併發所帶來的狀態一致性問題(不管是變量、仍是數據庫、緩存,我統稱它們爲狀態)。
咱們的程序擁有對狀態的徹底控制權,在任什麼時候候任何地方都可以修改狀態,能夠想象,若是咱們沒有對狀態進行有效的管理的話,就很容易形成混亂,維護性大大下降。
其實就跟我們一開始學編程的時候喜歡使用全局變量同樣,可是如今的問題更棘手。
若是說全局變量的影響是平面的,咱們只須要線性的去梳理修改這些狀態代碼的前後順序就可以解決BUG的話。
那麼加上併發競爭,這個影響就是3D的,排查BUG以及組織程序的複雜程度整整被提升了一個維度,由於空間與時間再也不是一一對應的關係了。
我以前寫的好幾篇文章,幾乎都是跟分佈式以及一致性相關的主題,如今本文又提到了這些問題,不少重複的內容我就不贅述了,有興趣的能夠翻翻我以前的文章。
關於硬件,我留下一個問題給有興趣的小夥伴,那就是爲何顯卡的計算能力大大超過了CPU?
難道就沒有一個行之有效的辦法來解決這些使人棘手的問題嗎?
若是你常常閱讀博客或者關注最新的技術文章和框架的話,你會發現,不少框架都開始慢慢支持以及完善從「同步」發展到「異步」的這個過程當中了。
咱們天天正在編寫的代碼是符合人腦思惟順序的,從上到下依次執行,好比x = 2,而後x = x * x,很容易就得出最後x == 4的結論。
其中x就是一個變量,可能儲存在CPU的寄存器當中,也可能儲存在堆內存中,但這不是重點,它們都在同一個計算機上。
同步就是依次執行,按照咱們所編寫代碼的順序逐步執行完全部的代碼,咱們利用分支判斷以及循環語句來控制執行的線路,依賴的是以前被計算好的狀態變量。
同步的缺陷很明顯,因爲是嚴格按照前後順序執行代碼的,這也是咱們預期的方式。
可是一旦涉及到IO操做,好比文件、網絡,整個程序的運行就會被阻塞,所以多線程能夠幫助咱們,將阻塞的操做與當前的流程分離開來,等讀取完後再去執行相關的操做。
咱們能夠經過回調或者事件的方式來異步的進行處理,所謂異步,簡單粗暴的理解就是咱們調用了一個函數,他不會立馬獲得結果,而同步就能夠獲得結果,哪怕時間再長,咱們也等(阻塞)。
能夠想象異步增長了代碼的複雜程度,由於原本同步是直接返回結果的,異步就須要咱們在另外一個處理單元等待喚醒而後繼續操做。
另外,同步的代碼只能在一個CPU當中執行,而多線程異步則能夠利用計算機上面其餘的CPU,使其並行執行提升效率。
想象一下若是咱們有一個函數,它不會修改任何狀態,僅僅是對參數進行計算,而後返回計算結果,而後咱們將這個函數分佈到不一樣的計算機上去執行,是否是就能不受制於單臺計算機天花板的影響了呢?
因爲這個函數不會修改任何狀態,不會有任何的反作用,因此它能夠在任何地方執行而不須要依賴其餘的條件,這種函數被稱之爲純函數。
純函數就像是一個能夠被隨時移動到不一樣地方去執行的單元。
所以,純函數就能夠被當作一個異步等待喚醒的處理器,咱們不知道它會在何時被執行,但咱們能夠放心,由於它不會致使反作用,也不須要依賴其餘的前置條件。
你或許注意到了本文所講的內容其實就是Actor模型,沒錯,你能夠認爲每個Actor就是一個個純函數。
但不管如何,你是自由的,你能夠在actor執行單元裏面作任何事情,可是請記住純函數不得引起任何的外部狀態修改,這是原則也是根本,由於咱們不想要反作用。
因此在純函數式編程「語言」裏面,根本就沒有賦值操做,不過是描述對輸入進行處理,而後返回結果而已,這可以讓咱們少犯一些錯誤。
如今,處理器咱們有了(純函數),它能夠被當作異步處理單元在任何計算機上面執行,那麼狀態呢?
注意我並不會介紹函數式編程的全部特性,有關這方面的資料我相信已經存在了。
什麼是命令式?什麼又是聲明式?
舉個例子,還記得你爲何用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)這個讓人用的上癮的強大工具,當你瀏覽了愈來愈多的新開源項目,或者是已經存在的開源項目開始慢慢過渡的轉變趨勢。
你會發現,不少知識概念都是通用且能夠互相轉換的,異步、分佈式、非阻塞、事件驅動、反應式等等…
而這些就是面向將來的編程知識,不管使用何種語言、框架或者工具庫。