2015年11月9日 星期一

函數式編程介紹 ( 1 / 2 )

函數式編程的專有名詞篇


純函數 ( Pure Function )給同樣輸入參數,就會回傳同樣結果的函數,而且沒有任何可觀察到的副作用。Pure Function 的例子:sin(x)。非 Pure Function 的例子:getChar()、random()、還有許多類別中的 member function。從數學上來理解,純函數就是一個數學函數,一個輸入會對到一個輸出。

純函數的特點:
  1. Portable / Self-Documenting :完全是自給自足的 ( self contained ),沒有外在的依賴 (Dependency)。
  2. 可暫存 ( Cacheable ):把計算值暫存的技巧被稱作 memorization,可以用來避免重複計算,加速程式。
  3. 好測試 ( Testable ):不需要管上下文和呼叫順序就可以測試。
  4. 適合平行處理、純函數是線程安全的 ( thread safe )。
  5. 引用透明 ( Referential Transparent ):一個引用透明的運算式指的是 如果這個運算式可以被他的值 (回傳值) 替換而不影響整個程式的行為。簡單講就是有可交換性啦。
  6. 可以熱抽換 ( Hot-Loading ):因為不依賴外部的狀態。

副作用 ( Side Effect )如果一個函數或運算式 ( expression ) 被說有副作用,這指的是它改變了一些狀態 ( states ) 或是 跟呼叫他的函數或外在世界,有可觀察到的互動。舉例來說,一個函數可能會改變全域變數或函數的靜態變數、改變傳進來的參數、引發例外 ( exception )、列印資料到螢幕或是呼叫了其他有副作用的函數。如果有了副作用,函數的行為會受到歷史、執行順序的影響。這樣子一來,想要理解或除錯有副作用的函式會較難,因為就必須要了解他的上下文 ( Context ) 和執行歷史。

顯式 ( Explicit ):形容函數与外界交換資料只有一個唯一管道——参數和回傳值。和顯式的相反是隱式 ( Implicit )。

一級函數( First-Class Function ):指的是語言支援把 Function 當成第一類公民,可以支援一般變數的操作,像是把 Function 當成變數傳給另外一個 Function。

Lambda Function:Lambda 運算式是匿名函式,可用來建立委派或運算式樹狀架構類型。

形式系統 ( Formal System ):形式系統可以的廣泛地被定義為任何基於數學模型的、良好的抽象思考的系統 ( system of abstract thought )。

Currying:這個技巧能把接受多參數的函數轉換為多個連續呼叫的單一參數函數。

閉鎖 ( Closure ) / MDN:Closure 是可以使用獨立 / 自由變數的函數。換句話說,Closure 記得它實體化時的環境變數。

PS:專有名詞那邊引用了很多別人的解釋,可以點前面的連結進去看完整版。

-----------------------------------------------------------------------------------------


函數式編程簡介篇

函數式編程的的哲學就是假設副作用 ( side effect ) 是不正確行為的主要原因。所以努力想要控制和管理副作用,經常的解法就是把純函數、單子和不純的函數 ( impure function ) 分開來管理。另外鼓勵大家多寫純函數。我們對待資料要像玩戲法般,一直傳來傳去、禁止使用狀態 ( state ) 和副作用。剛剛這段文字有提到很多專有名詞,但這樣子怎麼寫程式?這邊我開始介紹一個新工具叫 柯里化 ( currying )。

Currying:
Currying 把任何的函數轉換成 一連串只做單一事情的函數,各個擊破。

Curring的概念是簡單的。它讓你呼叫函數 A 時可以傳比預期還少的參數。然後這個函數 A 會回傳函數 B函數 B 需要的參數是先前沒傳進函數 A 的參數。所以整個流程是:

函數A (x, y) 可以等價於下面這段
-------------------------------------
函數B = 函數A (x)
函數B (y)

先前傳進去函數 A 的參數會利用閉鎖 ( Closure ) 的方式變成函數 B 的環境變數。看完上面這段解說,一定會想這什麼鬼東西?來看看 這邊的例子 會容易理解的多。利用這個技巧,就可以選擇一次傳所有參數或是把參數分幾次傳給數個函數。

lodash提供了把函數 curry化的工具,用法:

var divide =           function (x,y) { return x / y } 可以被改寫成
-----------------------------------------------------
var divide = curry( function (x,y) { return x / y } );

之後就可以被這樣呼叫

divide ( x ) ( y );

或是

var xDividedBy = divide ( x );
tenDividedBy ( y );

PS:這工具的名字是為了紀念一個美國數學家 --- Haskell Curry,跟咖哩沒有關係。

接下來我們看另外一個工具 代碼組成( compose )。

組合 / 組成 / 合成 ( compose )
Composition 把許多函數用管子 ( pipe ) 連接起來,變成一個新個函數。


f, g 都是函數,x 在它們之間被傳遞。
注意g函數的參數是 x、f函數的參數是 g函數的回傳值。
較常用在 f & g 都吃同一類參數的時候 (ex: 字串)。


例子:變大寫和去空白函數 = compose (變大寫函數, 去空白函數)

因為有了一級函數、Currying、組合,Pointfree 風格變得流行了起來,指的是函數呼叫時不用提到它要處理的資料。例如:

var snakeCase = function ( word ) {
      return word.toUpperCase( ).replace( /\s+/ig, '_' );


可被改寫成
-----------------------------------------------
var snakeCase = compose ( replace( /\s+/ig'_' ), toLowerCase );

用組合形成新函數就不用提到資料 --- 也就是之前寫法中的 word

Pointfree的編程風格可以讓我們移除不需要的名字 (names),讓我們保持簡潔 ( concise ) 和一般化 / 泛型 ( generic )。但要小心 Pointfree 是個雙面刃,有時會把原來的目的變得模糊。

( 以上是書本前五章的內容,其他待續... 後面有點硬,不知道什麼時候會看。)

---
這篇文介紹得很好 函數式編程

沒有留言:

張貼留言