摘要:函數式編程的哲學就是假定副作用是造成不正當行為的主要原因。函數組合面向對象通常被比喻為名詞,而函數式編程是動詞。尾遞歸優化函數式編程語言中因為不可變數據結構的原因,沒辦法實現循環。
零、前言
說到函數式編程,想必各位或多或少都有所耳聞,然而對于函數式的內涵和本質可能又有些說不清楚。
所以本文希望針對工程師,從應用(而非學術)的角度將函數式編程相關思想和實踐(以 JavaScript 為例)分享給大家。
文章內容其實主要來自于在下閱讀各類參考文獻后的再整理,所以有什么錯誤也希望大家幫忙斧正~
slide 地址
一、什么是函數式編程?Functional programming is a programming paradigm1.treats computation as the evaluation of mathematical functions
2.avoids changing-state and mutable data
by wikipedia
從以上維基百科的定義來看有三個要點
Programming Paradigm:編程范式
Mathematical Functions:數學函數
Changing-state And Mutable Data:改變狀態和可變數據
下面分別解析一下以上要點。
1.1.什么是編程范式?from Programming paradigms
編程范式從概念上來講指的是編程的基本風格和典范模式。
換句話說其實就是程序員對于如何使用編程來解決問題的世界觀和方法論。
如果把一門編程語言比作兵器,它的語法、工具和技巧等是招法,那么它采用的編程范式也就是是內功心法。
一種范式可以在不同的語言中實現,一種語言也可以同時支持多種范式。例如 JavaScript 就是一種多范式的語言。
1.2.什么是數學函數?一般的,在一個變化過程中,假設有兩個變量 x、y,如果對于任意一個 x 都有唯一確定的一個y和它對應,那么就稱 x 是自變量,y 是 x 的函數。x 的取值范圍叫做這個函數的定義域,相應 y 的取值范圍叫做函數的值域。
以上定義,在初中數學咱們都應該學過...
換句話說,函數只是兩種數值之間的關系:輸入和輸出。
盡管每個輸入都只會有一個輸出,但不同的輸入卻可以有相同的輸出。下圖展示了一個合法的從 x 到 y 的函數關系;
與之相反,下面這張圖表展示的就不是一種函數關系,因為輸入值 5 指向了多個輸出:
1.2.1.什么是純函數(Pure Functions)?純函數是這樣一種函數,對于相同的輸入,永遠會得到相同的輸出,而且沒有任何可觀察的副作用。
根據定義可以看出純函數其實就是數學函數,即表示從輸入的參數到輸出結果的映射。
而沒有副作用的純函數顯然都是引用透明的。
引用透明性(Referential Transparency)指的是,如果一段代碼在不改變整個程序行為的前提下,可以替換成它的執行結果。
const double = x => x * 2 const addFive = x => x + 5 const num = double(addFive(10)) num === double(10 + 5) === double(15) === 15 * 2 === 30
不過說了半天,副作用又是啥...?
1.2.2.什么是副作用(Side Effects)?副作用是在計算的過程中,系統狀態的一種變化,或者與外部世界進行的可觀察的交互。
副作用可能包含,但不限于一下行為:
更改文件系統
往數據庫中插入記錄
發送一個 http 請求
改變數據
打印 log
獲取用戶輸入
DOM 查詢
訪問系統狀態
...
只要是跟函數外部環境發生的交互就都是副作用——這一點可能會讓你懷疑無副作用編程的可行性。函數式編程的哲學就是假定副作用是造成不正當行為的主要原因。
當然這并不是說,要禁止使用一切副作用,而是說,要讓它們在可控的范圍內發生。
在后面講到函子(functor)和單子(monad)的時候我們會學習如何控制它們。
1.2.3.純函數的好處都有啥面向對象語言的問題是,它們永遠都要隨身攜帶那些隱式的環境。你只需要一個香蕉,但卻得到一個拿著香蕉的大猩猩...以及整個叢林by Erlang 作者:Joe Armstrong
所以使用純函數將會有以下好處:
可緩存性(Cacheable)
可移植性/自文檔化(Portable / Self-Documenting)
可測試性(Testable)
合理性(Reasonable)
并行代碼(Parallel Code)
1.3.為什么要避免改變狀態和可變數據?Shared mutable state is the root of all evil共享可變狀態是萬惡之源
by Pete Hunt
const obj = { val: 1 } someFn(obj) console.log(obj) // ???
from Building Scalable, Highly Concurrent & Fault Tolerant Systems - Lessons Learned1.4.原教旨函數式 VS 溫和派函數式?
說到函數式編程語言,大家的第一反應可能是 Haskell、OCaml、Lisp、Erlang、Scala、F#...
因為它們可能有以下特性:
函數是“一等公民”(first class)
不可變數據
使用遞歸而不是循環
柯里化
惰性求值
代數數據類型
模式匹配
...
而說到 JavaScript,很多人可能第一反應認為這是一門面向對象的語言。
但是想想前面說的:函數式編程只是一種編程范式,而編程范式就像“內功心法”,所以與以上這些語言特性不完全相關,反而與你自己的編程思維(即世界觀和方法論)更加相關。
在函數式方面,由于 JavaScript 支持高階函數、匿名函數、函數是一等公民、閉包、解構(模式匹配)等特性,所以它也能支持函數式編程范式。(雖然不是那么的原教旨函數式,但還基本夠用~尤其是 ES6 新增的箭頭函數等特性~還有各種類庫 )
事實上 JavaScript 是一門基于原型(prototype-based)的多范式語言。
1.5.作為函數式語言 JavaScript 還差什么? 1.5.1.不可變數據結構JavaScript 一共有 6 種原始類型(包括 ES6 新添加的 Symbol 類型),它們分別是 Boolean,Null,Undefined,Number,String 和 Symbol。 除了這些原始類型,其他的類型都是 Object,而 Object 都是可變的。
1.5.2.惰性求值惰性(lazy)指求值的過程并不會立刻發生。
比如一些數學題,我們可能一開始并不需要把所有表達式都求值,這樣可以在計算的過程中將一些表達式消掉。
惰性求值是相對于及早求值(eager evaluation)的。
比如大部分語言中,參數中的表達式都會被先求值,這也稱為應用序語言。
比如看下面這樣一個 JavaScript 的函數:
wholeNameOf(getFirstName(), getLastName())
getFirstName 與 getLastName 會依次執行,返回值作為 wholeNameOf 函數的參數, wholeNameOf 函數最后才被調用。
另外,對于數組操作時,大部分語言也同樣采用的是應用序。
[1, 2, 3, 4].map(x => x + 1)
所以,這個表達式立刻會返回結果 [2, 3, 4, 5] 。
當然這并不是說 JavaScript 語言使用應用序有問題,但是沒有提供惰性序列的支持就是 JavaScript 的不對了。如果 map 一個大數組后我們發現其實只需要前 10 個元素時,去計算所有元素就顯得是多余的了。
1.5.3.函數組合面向對象通常被比喻為名詞,而函數式編程是動詞。面向對象抽象的是對象,對于對象的的描述自然是名詞。
面向對象把所有操作和數據都封裝在對象內,通過接受消息做相應的操作。比如,對象 Kitty,它們可以接受“打招呼”的消息,然后做相應的動作。
而函數式的抽象方式剛好相反,是把動作抽象出來,比如“打招呼”就是一個函數,而函數參數就是作為數據傳入的 Kitty(即 Kitty 進入函數“打招呼”,出來的應該是 Hello Kitty)。
面向對象可以通過繼承和組合在對象之間分享一些行為或者說屬性,函數式的思路就是通過組合已有的函數形成一個新的函數。
然而 JavaScript 語言雖然支持高階函數,但是并沒有一個原生的利于組合函數產生新函數的方式。而這些強大的函數組合方式卻往往被類似 Underscore,Lodash 等工具庫的光芒掩蓋掉(后面會說到這些庫的問題)。
1.5.4.尾遞歸優化函數式編程語言中因為不可變數據結構的原因,沒辦法實現循環。所以都是通過遞歸來實現循環。
然而遞歸使用不當很容易棧溢出(Stack Overflow),所以一般采用尾遞歸的方式來優化。
雖然 ES6 規范中規定了尾遞歸優化規范,然而提供實現的解釋器還非常的少,詳情可以查閱這個鏈接
5.代數類型系統JavaScript 作為一種弱類型的語言,沒有靜態類型系統。不過使用一些 TypeScript 等預編譯的語言可以作為補充~
二、聲明式 VS 命令式Declarative VS Imperative,這兩者的區別簡單來說其實就是 What VS How。
2.1.“意識形態”上的區別~聲明式:
程序抽象了控制流過程,代碼描述的是 —— 數據流:即做什么。
更多依賴表達式。
表達式是指一小段代碼,它用來計算某個值。表達式通常是某些函數調用的復合、一些值和操作符,用來計算出結果值。
命令式:
代碼描述用來達成期望結果的特定步驟 —— 控制流:即如何做。
頻繁使用語句。
語句是指一小段代碼,它用來完成某個行為。通用的語句例子包括 for、if、switch、throw,等等……2.2.舉一些栗子
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/96874.html
摘要:函數式編程,一看這個詞,簡直就是學院派的典范。所以這期周刊,我們就重點引入的函數式編程,淺入淺出,一窺函數式編程的思想,可能讓你對編程語言的理解更加融會貫通一些。但從根本上來說,函數式編程就是關于如使用通用的可復用函數進行組合編程。 showImg(https://segmentfault.com/img/bVGQuc); 函數式編程(Functional Programming),一...
摘要:所以我覺得函數式編程領域更像學者的領域。函數式編程的原則是完善的,經過了深入的研究和審查,并且可以被驗證。函數式編程是編寫可讀代碼的最有效工具之一可能還有其他。我知道很多函數式編程編程者會認為形式主義本身有助于學習。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 關于譯者:這是一個流淌著滬江血液...
摘要:為了盡可能提升互通性,已經成為函數式編程庫遵循的實際標準。與輕量級函數式編程的概念相反,它以火力全開的姿態進軍的函數式編程世界。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關于譯者:這是一個流淌著滬江血液的純粹工程:認真,是 HTML 最堅實的梁柱;分享,是 CSS 里最閃耀的一瞥;總結,...
摘要:今天這篇文章主要介紹函數式編程的思想。函數式編程通過最小化變化使得代碼更易理解。在函數式編程里面,組合是一個非常非常非常重要的思想。可以看到函數式編程在開發中具有聲明模式。而函數式編程旨在盡可能的提高代碼的無狀態性和不變性。 最開始接觸函數式編程的時候是在小米工作的時候,那個時候看老大以前寫的代碼各種 compose,然后一些 ramda 的一些工具函數,看著很吃力,然后極力吐槽函數式...
摘要:函數式編程一開始我并不理解。漸漸地,我熟練掌握了使用函數式的方法去編程。但是自從學習了函數式編程,我將循環都改成了使用和來實現。只有數據和函數,而且因為函數沒有和對象綁定,更加容易復用。在函數式的中,這些問題不復存在。 譯者按: 當從業20的JavaScript老司機學會函數式編程時,他扔掉了90%的特性,也不用面向對象了,最后發現了真愛啊!!! 原文: How I rediscov...
閱讀 2382·2021-11-24 10:26
閱讀 2582·2021-11-16 11:44
閱讀 1699·2021-09-22 15:26
閱讀 3570·2021-09-10 11:11
閱讀 3185·2021-09-07 10:25
閱讀 3624·2021-09-01 10:41
閱讀 1008·2021-08-27 13:11
閱讀 3505·2021-08-16 11:02