函數(shù)式編程的理念

1. FP 理念

1.1 不變性

  • 沒(méi)有變量的概念, 只有'值'.
    • 避免改變狀態(tài)及可變數(shù)據(jù).
    • 三部曲: 編寫(xiě)函數(shù), 使用REPL工具測(cè)試, 使用.

1.2 聲明性風(fēng)格

  • 代碼是描述期望結(jié)果的表達(dá)式.
    • 將表達(dá)式組合起來(lái), 可以同時(shí)隱藏執(zhí)行細(xì)節(jié)和復(fù)雜性.
  • 聲明性只提出what to do 而不是解決how to do.
    • 例如, 使用foreach 來(lái)遍歷集合數(shù)據(jù), 其實(shí)是在重復(fù)how to do(命令式).
    • 提取通用的'思想'為詞語(yǔ)(函數(shù)名), 已經(jīng)是what to do 了.
// csharp
return sourceList.Where(item => item %2 == 0);
// or LINQ 
stylereturn from item in sourceList where item % 2 == 0 select item;
// if we have a function(abstract) already. 
return sourceList.Where(NumberIsEven);
  • C#的LINQ借鑒的是FP的高階函數(shù)以及monad,只是和SQL長(zhǎng)的比較像而已(可能是為了避免學(xué)習(xí)成本)。

1.3 類(lèi)型

  • 每個(gè)表達(dá)式都有對(duì)應(yīng)的類(lèi)型, 這確保了表達(dá)式組合的正確性.
  • 泛型是一種代碼重用技術(shù).
    • 使用類(lèi)型占位符來(lái)將真正的類(lèi)型延遲到運(yùn)行時(shí)再?zèng)Q定.
    • 類(lèi)型模板, 在需要時(shí)插入真實(shí)的類(lèi)型.
    • 包裝: 打包了某種具體類(lèi)型.
      • 包裝類(lèi): int?其實(shí)是指Nullable<T>對(duì)int類(lèi)型的包裝。

1.4 表達(dá)式求值

  • 整個(gè)程序就是一個(gè)大的表達(dá)式.
    • 關(guān)心的是表達(dá)式的副作用, 而不是其返回值.
    • 沒(méi)有語(yǔ)句的概念.

2. 高階函數(shù)

  • 方便函數(shù)的定制.
    • 隱藏具體的執(zhí)行細(xì)節(jié), 將可定制的部分(行為)抽象處理傳遞給某個(gè)高階函數(shù)使用.
    • 類(lèi)似OO 的模板方法.
  • 提升
    ('a -> 'b) -> M<'a> -> M<'b>
    lift2: ('a -> 'b -> 'c) -> M<'a> -> M<'b> -> M<'c>
    • 允許將一個(gè)對(duì)值進(jìn)行處理的函數(shù)轉(zhuǎn)換為一個(gè)在不同設(shè)置中完成相同任務(wù)的函數(shù).

3. 柯里化(Currying) 和部分函數(shù)應(yīng)用

  • 柯里化(Currying)
function addBy(value) 
{ 
  return function(n) {  
    // 將value 的進(jìn)行閉包處理
    return n + value; 
  };
}
var add10 = addBy(10);
var result11 = add10(1);

// orElse
var result11 = addBy(10)(1);
  • 多個(gè)參數(shù)時(shí):
    let fakeAddFn n1 = fun n2 -> fun n3 -> fun n4 -> n1 + n2 + n3 + n4

4. 遞歸及優(yōu)化

  • 尾遞歸優(yōu)化:
    • 當(dāng)在遞歸調(diào)用的時(shí)候,不需要做任何工作, 則可以從當(dāng)前的調(diào)用棧直接跳到下一次的調(diào)用棧上去.
    • 關(guān)鍵是對(duì)累加器的使用.
    • 當(dāng)需要處理非常龐大的列表時(shí).
public int Factorial(int n) {
    return n <= ? 1 : n * Factorial(n - 1);
}
>> 每次遞歸都卡在了n*_ 上, 必須等后面的結(jié)果返回后,當(dāng)前函數(shù)的調(diào)用棧才能返回.
n               (n-1)       ...      3         2       1  // state
--------------------------------------------------------
n*f(n-1) -> (n-1)*f(n-2) -> ... -> 3*f(2) -> 2*f(1) -> 1  // stack in
                                                       |  
n*r      <-  (n-1)*(r-1) <- ... <-   3*2  <-   2*1  <- 1  // stack out

private int FactorialHelper(acc, n) {
    return n <= 1 ? acc : FactorialHelper(acc * n, n - 1);
}
public int Factorial(int n) { return FactorialHelper(1, n); }

init        f(1, n)             // stack in
                |               // stack pop, jump to next
n           f(n, n-1)           // stack in
                |               // stack pop, jump to next
n-1         f(n*(n-1), n-2)     // stack in
                |               // stack pop, jump to next
...         ...                 // stack in
                |               // stack pop, jump to next
2           f((k-1), 1)         // stack in
                |               // stack pop, jump to next
1           k                   // return result
  • fold 自帶累加器的化簡(jiǎn)函數(shù), 抽取了遍歷并化簡(jiǎn)核心的步驟, 僅將需要自定義的部分以參數(shù)的形式放出來(lái).
    • fold 的累加器需要指定一個(gè)初始值, 而reduce 的初始累加器使用列表的第一個(gè)值.

5. 記憶化

  • FP 函數(shù)是沒(méi)有副作用的
    • 也就意味著以相同的參數(shù)調(diào)用同一函數(shù)將會(huì)返回相同的結(jié)果.
    • 對(duì)于一些會(huì)被多次調(diào)用的函數(shù), 將對(duì)應(yīng)參數(shù)的求值結(jié)果緩存起來(lái), 以后遇到相同的參數(shù)直接返回緩存結(jié)果.
  • 通用的記憶化函數(shù)
let memorize f =
    let cache = new Dictionary<_, _>()
    fun p ->
        match cache.TryGetValue(p) with
        | true, result -> result
        | _ ->
            let result = f p
            cache.Add(p, result)
            result

6. 惰性求值

  • 立刻求值: 將表達(dá)式n % 2 == 0 ? "right" : "wrong"綁定到標(biāo)識(shí)(即變量名)isEven上
  • 將isEven 綁定到某種結(jié)構(gòu)上, 結(jié)構(gòu)知道如何求值,并且是按需求求值.
    • var isEven = new Lazy<string> (() => n % 2 == 0 ? "right" : "wrong");
    • 使用isEven.value 來(lái)即可求值并拿到結(jié)果.
  • 通過(guò)函數(shù),函數(shù)表達(dá)了某種可以得到值的方式,但是需要調(diào)用才能得到.
    • var isEven = (Func<string>)(() => n % 2 == 0 ? "right" : "wrong");
    • 通過(guò)函數(shù)調(diào)用isEven() 來(lái)立刻獲取值.

7. 延遲

  • 回調(diào)函數(shù): 延后了某種行為, 且該行為對(duì)上下文有依賴(lài).
  • 手段: 在函數(shù)調(diào)用結(jié)束后自動(dòng)調(diào)用指定的行為(函數(shù)).
    • 在進(jìn)行尾遞歸優(yōu)化時(shí), 之前累加器累加的值, 延遲使得可以累加行為.
  • 延遲可以推導(dǎo)出bind: 外層匹配模式.
// binding:
('a -> 'b)    -> 'a -> 'b
// map:
('a -> 'b)    -> M<'a> -> M<'b>
// bind:
('a -> M<'b>) -> M<'a> -> M<'b>

10. Mics

  • 一等用來(lái)描述'值', 函數(shù)被看做一等公民, 就是函數(shù)等價(jià)于'值'的概念.
  • 高階函數(shù): 以一個(gè)函數(shù)(而不是值)作為參數(shù); 返回一個(gè)函數(shù)作為結(jié)果.
  • 編程方式
    • 命令式編程: 建立在直接操作和檢查程序狀態(tài)之上.
      • 局限于細(xì)節(jié).
      • 函數(shù)式的思路是將程序拆分并抽象成多個(gè)函數(shù)再組裝回去.
    • 面向?qū)ο缶幊?
      • js 是利用現(xiàn)有的對(duì)象作為原型來(lái)生產(chǎn)特定的實(shí)例.
      • 必須有一個(gè)語(yǔ)義的自引用(this).
    • 元編程
      • 可以通過(guò)編寫(xiě)代碼來(lái)改變某些代碼被解釋的方式.
      • this 引用的動(dòng)態(tài)性質(zhì)可以用來(lái)元編程.
  • 集合中心編程.
    • 用100 個(gè)函數(shù)操作一個(gè)數(shù)據(jù)結(jié)構(gòu), 比用10 個(gè)函數(shù)操作10 個(gè)數(shù)據(jù)結(jié)構(gòu)要好.
  • 閉包的兩個(gè)重點(diǎn)
    • 閉包會(huì)捕獲一個(gè)值(或引用), 并多次返回相同的值.
    • 每個(gè)新的閉包都會(huì)捕獲不一樣的值.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容