1. 程式人生 > >什麽是函數式編程

什麽是函數式編程

變化 n) 不同 utm 通過 不包含 return href 關系

導讀

建議先閱讀一下這幾篇博客:
函數式編程初探
函數式編程入門教程
圖解 Monad

什麽是函數式編程

函數式編程中的函數指的並不是編程語言中的函數(或方法),它指的是數學意義上的函數,即映射關系(如:y = f(x)),就是 y 和 x 的對應關系。

數學上對於函數的定義是這樣的:“給定一個數集 A,假設其中的元素為 x。現對 A 中的元素 x 施加對應法則 f,記作 f(x),得到另一數集 B。假設 B 中的元素為 y。”

所以當我們在討論“函數式”時,我們其實是在說“像數學函數那樣,接收一個或多個輸入,生成一個或多個結果,並且沒有副作用(稍後解釋)”
技術分享圖片

實際上當一個函數無論在何處、何時調用,如果使用相同的輸入總能持續地得到相同的結果,我們就可以說他具備了函數式的特征。

需要註意的是,純函數式編程語言中的變量指的也不是命令式編程語言中的變量(即儲存狀態的單元),而是代數的變量,表示一個未知的數,或者一個可帶入函數的值。
(在代數中對於“數本身是什麽”這樣的問題並不關心,它關心是各種關系及其性質)

所以函數式編程語言中所說的變量只是一個變量的名稱,變量的值是不可變的(immutable),也就是說不允許像命令式編程語言中那樣多次給一個變量賦值。
(PS:我們真的需要變量嗎?)

函數是語言中的變量:

  • 變量的值一旦綁定就不再發生變化
  • 變量中存放的與其說是指,不如說是表達式(惰性求值)

函數式編程語言的特性

在了解了函數編程語言的基本概念之後,我們再來看一下函數式編程語言所具備一些特性,理解這些特性將有助於我們更好的理解“什麽是函數式編程”。

一,函數是一等公民

函數是一等公民,它的意思就是函數與其他數據類型一樣,可以把它們存在數組裏,當做參數傳遞,賦值給變量,可以在任何地方定義,在函數內或函數外,可以作為函數的參數和返回值,也可以對函數進行組合。

二,高階函數

在函數式編程中,如果函數能滿足下面任一要求就可以被稱為高階函數(higher-order function):

  • 接受至少一個函數作為參數
  • 返回的結果是一個函數

三,柯裏化

所謂“柯裏化” ,就是把一個多參數的函數 f,轉換為單參數函數 g,並且這個函數的返回值也是一個函數。

// 柯裏化之前
function add(x, y) {
  return x + y;
}
add(1, 2) // 3
// 柯裏化之後
function addX(y) {
  return function (x) {
    return x + y;
  };
}
addX(2)(1) // 3

四,副作用

所謂“副作用”,指的是函數內部與外部互動(最典型的情況,就是修改全局變量的值),產生運算以外的其他結果。
在像 C++ 這樣的命令式語言中,函數的意義與數學函數完全不同。例如,假設我們有一個 C++ 函數,它接受一個浮點參數並返回一個浮點結果。從表面上看它可能看起來有點像數學函數意義上的映射實數成實數,但是 C++ 函數可以做的不僅僅是返回一個取決於其參數的數字,它還可以讀寫其他的全局變量,也可將將輸出寫入屏幕並接收來自用戶的輸入。但是,在純函數式語言中,函數只能讀取其參數提供給它的內容,並且它對世界產生影響的唯一方式就是通過它返回的值。

五,純函數

純函數編程和函數編程的區別在於:是否允許在函數內部執行一些非函數式的操作,同時這些操作是否會暴露給系統中的其他地方?也就是是否存在副作用。如果不存在副作用,或者說可以不用在意這些副作用,那麽就將其稱為純粹的函數式編程。

六、引用透明性

函數無論在何處、何時調用,如果使用相同的輸入總能持續地得到相同的結果,就具備了函數式的特征。這種不依賴外部變量或“狀態”,只依賴輸入的參數的特性就被稱為引用透明性(referential transparency)。
“沒有可感知的副作用”(比如不改變對調用者可見的變量,進行I/O,不拋出異常等)的這些限制都隱含著引用透明性

五,遞歸和叠代

對於函數式而言,循環體有一個無法避免的副作用,就是它會修改某些對象的狀態,通常這些對象又是和其他部分共享的。而且也因為變量值是不可變的,純函數編程語言也無法實現循環。
所以純函數編程語言通常不包含像 while 和 for 這樣的叠代構造器,而是采用的無需修改的遞歸。
下面是一段標準的基於循環的方式來計算階乘:

static int factorialIterative(int n){
    int r = 1;
    for (int i = 1; i <= n; i++) {
        r *= 1;
    }
    return r;
}

而采用遞歸形式的方式如下:

static long factorialRecursive(long n){
    return n == 1 ? 1 : n * factorialRecursive(n-1)
}

擴展閱讀
http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html
https://www.zhihu.com/question/28292740
https://blog.csdn.net/qq_31179577/article/details/78745248

什麽是函數式編程