<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                [TOC] # 范疇論 ## 范疇 范疇就是使用箭頭連接的物體。也就是說,彼此之間存在某種關系的概念、事物、對象等等,都構成"范疇"。隨便什么東西,只要能找出它們之間的關系,就能定義一個"范疇"。 <br> ![](http://www.ruanyifeng.com/blogimg/asset/2017/bg2017022210.jpg) <br> 上圖中,各個點范疇的成員,與它們之間的箭頭,就構成一個范疇。 <br> 箭頭表示范疇成員之間的關系,正式的名稱叫做"態射"(morphism)。范疇論認為,同一個范疇的所有成員,就是不同狀態的"變形"(transformation)。通過"態射",一個成員可以變形成另一個成員。 <br> <br> ## 范疇與容器 我們可以把"范疇"想象成是一個容器,里面包含兩樣東西。 * 值(value) * 值的變形關系,也就是函數。 <br> 下面我們使用代碼,定義一個簡單的范疇。 ``` class Category { constructor(val) { this.val = val; } addOne(x) { return x + 1; } } ``` 上面代碼中,Category是一個類,也是一個容器,里面包含一個值(this.val)和一種變形關系(addOne)。你可能已經看出來了,這里的范疇,就是所有彼此之間相差1的數字。 <br> ## 范疇論與函數式編程的關系 范疇論使用函數,表達范疇之間的關系。 <br> 伴隨著范疇論的發展,就發展出一整套函數的運算方法。這套方法起初只用于數學運算,后來有人將它在計算機上實現了,就變成了今天的"函數式編程"。 <br> 本質上,函數式編程只是范疇論的運算方法,跟數理邏輯、微積分、行列式是同一類東西,都是數學方法,只是碰巧它能用來寫程序。 <br> <br> # 函數式編程的特點 * 函數是”第一等公民”。所謂”第一等公民”(first class),指的是函數與其他數據類型一樣,處于平等地位,可以賦值給其他變量,也可以作為參數,傳入另一個函數,或者作為別的函數的返回值。 * 只用”表達式",不用"語句"。 * 沒有”副作用"。如果函數與外部可變狀態進行交互,則它是有副作用的。如: * 修改一個變量 * 直接修改數據結構 * 設置一個對象的成員 * 拋出一個異常或以一個錯誤終止 * 打印到終端或讀取用戶的輸入 * 讀取或寫入一個文件 * 在屏幕上繪畫 * 不修改狀態。在函數式編程中變量僅僅代表某個表達式。這里所說的’變量’是不能被修改的。所有的變量只能被賦一次初值。 * 引用透明(函數運行只靠參數) <br> <br> # 函數式編程的優點與缺點 優點 * 效降低系統的復雜度 * 可緩存性 ``` import _ from 'lodash'; var sin = _.memorize(x =>Math.sin(x)); //第一次計算的時候會稍慢一點 var a = sin(1); //第二次有了緩存,速度極快 var b = sin(1); ``` <br> 缺點 * 擴展性比較差 ``` // 不純的 var min = 18; var checkage = age => age > min; // 純函數 var checkage = age => age > 18; ``` 在不純的版本中,checkage 不僅取決于 age還有外部依賴的變量 min。純的 checkage 把關鍵數字 18 硬編碼在函數內部,,柯里化優雅的函數式解決。 <br> <br> # 核心概念 ## 純函數(Purity) 對于相同的輸入,永遠會得到相同的輸出,而且沒有任何可觀察的副作用,也不依賴外部環境的狀態。 ``` const greet = (name) => `hello, ${name}` greet('world') ``` <br> 以下代碼函數依賴外部狀態,不是純函數: ``` window.name = 'Brianne' const greet = () => `Hi, ${window.name}` greet() // "Hi, Brianne" ``` <br> 以下代碼函數修改了外部狀態,不是純函數: ``` let greeting const greet = (name) => { greeting = `Hi, ${name}` } greet('Brianne') greeting // "Hi, Brianne" ``` <br> ## 冪等性 (Idempotent) 冪等性是指執行無數次后還具有相同的效果,同一的參數運行一次函數應該與連續兩次結果一致。冪等性在函數式編程中與純度相關,但有不一致。 ``` f(f(x)) = f(x) ``` <br> ``` Math.abs(Math.abs(10)) ``` <br> ``` sort(sort(sort([2, 1]))) ``` <br> ## 偏應用函數 (Partial Function) 傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數。 <br> ``` // 創建偏函數,固定一些參數 const partical = (f, ...args) => // 返回一個帶有剩余參數的函數 (...moreArgs) => // 調用原始函數 f(...args, ...moreArgs) const add3 = (a, b, c) => a + b + c // (...args) => add3(2, 3, ...args) // (c) => 2 + 3 + c const fivePlus = partical(add3, 2, 3) fivePlus(4) // 9 ``` <br> 也可以使用 Function.prototype.bind 實現偏函數。 ``` const add1More = add3.bind(null, 2, 3) ``` <br> 偏函數應用通過對復雜的函數填充一部分數據來構成一個簡單的函數。柯里化通過偏函數實現。 <br> 偏函數之所以“偏”,在就在于其只能處理那些能與至少一個case語句匹配的輸入,而不能處理所有可能的輸入。 <br> ## 柯里化 (Currying) 將一個多元函數轉變為一元函數的過程。 每當函數被調用時,它僅僅接收一個參數并且返回帶有一個參數的函數,直到傳遞完所有的參數。 ``` var checkage = min => (age => age > min); var checkage18 = checkage(18); checkage18(20); const sum = (a, b) => a + b const curriedSum = (a) => (b) => a + b curriedSum(3)(4) // 7 const add2 = curriedSum(2) add2(10) // 12 ``` <br> 優點 事實上柯里化是一種“預加載”函數的方法,通過傳遞較少的參數,得到一個已經記住了這些參數的新函數,某種意義上講,這是一種對參數的“緩存”,是一種非常高效的編寫函數的方法。 <br> ## 函數組合 (Function Composing) 接收多個函數作為參數,從右到左,一個函數的輸入為另一個函數的輸出。 ![](https://box.kancloud.cn/2135c8c4353848fd4d3665fd59d01040_330x316.png) ``` const compose = function (f, g) { return function (x) { return f(g(x)); }; } var first = arr => arr[0]; var reverse = arr => arr.reverse(); var last = compose(first, reverse); console.log(last([1,2,3,4,5])) // 5 ``` <br> 函數的合成還必須滿足結合律。 ![](https://box.kancloud.cn/debc078f43601b2b525c20ce8c90cfea_800x313.png) ``` compose(f, compose(g, h)) // 等同于 compose(compose(f, g), h) // 等同于 compose(f, g, h) ``` <br> ## Point-Free 把一些對象自帶的方法轉化成純函數,不要命名轉瞬即逝的中間變量。 <br> 這個函數中,我們使用了 str 作為我們的中間變量,但這個中間變量除了讓代碼變得長了一點以外是毫無意義的。 <br> ``` const f = str => str.toUpperCase().split(' '); ``` <br> 使用point-free風格: ``` var toUpperCase = word => word.toUpperCase(); var split = x => (str => str.split(x)); var f = compose(split(' '), toUpperCase); f("abcd efgh"); ``` <br> 這種風格能夠幫助我們減少不必要的命名,讓代碼保持簡潔和通用。 <br> ## 高階函數 (Higher-Order Function / HOF) 函數當參數,把傳入的函數做一個封裝,然后返回這個封裝函數,達到更高程度的抽象。 ``` // 命令式 var add = function(a,b){ return a + b; }; function math(func,array){ return func(array[0],array[1]); } math(add,[1,2]); // 3 ``` * 它是一等公民 * 它已一個函數作為參數 * 已一個函數作為返回結果 <br> ## 尾調用 ### 什么是尾調用 指函數內部的最后一個動作是函數調用。該調用的返回值,直接返回給函數。 ``` function f(x){ return g(x); } ``` 上面代碼中,函數f的最后一步是調用函數g,這就叫尾調用。 <br> 以下三種情況,都不屬于尾調用。 ``` // 情況一 function f(x){ let y = g(x); return y; } // 情況二 function f(x){ return g(x) + 1; } // 情況三 function f(x){ g(x); } ``` 上面代碼中,情況一是調用函數g之后,還有賦值操作,所以不屬于尾調用,即使語義完全一樣。 情況二也屬于調用后還有操作,即使寫在一行內。 情況三等同于下面的代碼。 ``` function f(x){ g(x); return undefined; } ``` <br> 尾調用**不一定出現在函數尾部**,**只要是最后一步操作即可**。 ``` function f(x) { if (x > 0) { return m(x) } return n(x); } ``` 上面代碼中,函數m和n都屬于尾調用,因為它們都是函數f的最后一步操作。 > 函數調用自身,稱為遞歸。 如果尾調用自身,就稱為尾遞歸。 遞歸需要保存大量的調用記錄,很容易發生棧溢出錯誤,如果使用尾遞歸優化,將遞歸變為循環,那么只需要保存一個調用記錄,這樣就不會發生棧溢出錯誤了。 <br> ### 尾調用優化 尾調用之所以與其他調用不同,就在于它的特殊的調用位置。 <br> 我們知道,函數調用會在內存形成一個“調用記錄”,又稱“調用幀”(call frame),保存調用位置和內部變量等信息。如果在函數A的內部調用函數B,那么在A的調用幀上方,還會形成一個B的調用幀。等到B運行結束,將結果返回到A,B的調用幀才會消失。如果函數B內部還調用函數C,那就還有一個C的調用幀,以此類推。所有的調用幀,就形成一個“調用棧”(call stack)。 <br> **尾調用由于是函數的最后一步操作,所以不需要保留外層函數的調用幀,因為調用位置、內部變量等信息都不會再用到了,只要直接用內層函數的調用幀,取代外層函數的調用幀就可以了。** ``` function f() { let m = 1; let n = 2; return g(m + n); } f(); // 等同于 function f() { return g(3); } f(); // 等同于 g(3); ``` <br> 上面代碼中,如果函數g不是尾調用,函數f就需要保存內部變量m和n的值、g的調用位置等信息。但由于調用g之后,函數f就結束了,所以執行到最后一步,完全可以刪除f(x)的調用幀,只保留g(3)的調用幀。 <br> 這就叫做“尾調用優化”(Tail call optimization),即只保留內層函數的調用幀。**如果所有函數都是尾調用,那么完全可以做到每次執行時,調用幀只有一項,這將大大節省內存**。這就是“尾調用優化”的意義。 <br> 注意,**只有不再用到外層函數的內部變量,內層函數的調用幀才會取代外層函數的調用幀**,否則就無法進行“尾調用優化”。 <br> ``` function addOne(a){ var one = 1; function inner(b){ return b + one; } return inner(a); } ``` <br> 上面的函數不會進行尾調用優化,因為內層函數inner用到了外層函數addOne的內部變量one。 <br> ### 傳統遞歸 普通遞歸時,內存需要記錄調用的堆棧所出的深度和位置信息。在最底層計算返回值,再根據記錄的信息,跳回上一層級計算,然后再跳回更高一層,依次運行,直到最外層的調用函數。在cpu計算和內存會消耗很多,而且當深度過大時,會出現堆棧溢出。 ``` function sum(n) { if (n === 1) return 1; return n + sum(n - 1); } // sum(5) // (5 + sum(4)) // (5 + (4 + sum(3))) // (5 + (4 + (3 + sum(2)))) // (5 + (4 + (3 + (2 + sum(1))))) // (5 + (4 + (3 + (2 + 1)))) // (5 + (4 + (3 + 3))) // (5 + (4 + 6)) // (5 + 10) // 15 ``` <br> ### 尾遞歸 整個計算過程是線性的,調用一次sum(x, total)后,會進入下一個棧,相關的數據信息和跟隨進入,不再放在堆棧上保存。當計算完最后的值之后,直接返回到最上層的sum(5,0)。這能有效的防止堆棧溢出。 在ECMAScript 6,我們將迎來尾遞歸優化,通過尾遞歸優化,javascript代碼在解釋成機器碼的時候,將會向while看齊,也就是說,同時擁有數學表達能力和while的效能。 ``` function sum(x, total) { if (x === 1) { return x + total; } return sum(x - 1, x + total); } // sum(5, 0) // sum(4, 5) // sum(3, 9) // sum(2, 12) // sum(1, 14) // 15 ``` <br> 還有一個比較著名的例子,就是計算 Fibonacci 數列,也能充分說明尾遞歸優化的重要性。 <br> 非尾遞歸的 Fibonacci 數列實現如下。 ``` function Fibonacci (n) { if ( n <= 1 ) {return 1}; return Fibonacci(n - 1) + Fibonacci(n - 2); } Fibonacci(10) // 89 Fibonacci(100) // 超時 Fibonacci(500) // 超時 ``` <br> 尾遞歸優化過的 Fibonacci 數列實現如下。 ``` function Fibonacci2 (n , ac1 = 1 , ac2 = 1) { if( n <= 1 ) {return ac2}; return Fibonacci2 (n - 1, ac2, ac1 + ac2); } Fibonacci2(100) // 573147844013817200000 Fibonacci2(1000) // 7.0330367711422765e+208 Fibonacci2(10000) // Infinity ``` <br> 尾遞歸的判斷標準是函數運行【最后一步】是否調用自身,而不是是否在函數的【最后一行】調用自身,最后一行調用其他函數并返回叫尾調用。 <br> 按道理尾遞歸調用調用棧永遠都是更新當前的棧幀而已,這樣就完全避免了爆棧的危險。但是現如今的瀏覽器并未完全支持。原因有二: * 在引擎層面消除遞歸是一個隱式的行為,程序員意識不到。 * 堆棧信息丟失了 開發者難已調試 <br> ### 嚴格模式 ES6 的尾調用優化只在嚴格模式下開啟,正常模式是無效的。 這是因為在正常模式下,函數內部有兩個變量,可以跟蹤函數的調用棧。 * `func.arguments`:返回調用時函數的參數。 * `func.caller`:返回調用當前函數的那個函數。 <br> 尾調用優化發生時,函數的調用棧會改寫,因此上面兩個變量就會失真。嚴格模式禁用這兩個變量,所以尾調用模式僅在嚴格模式下生效。 ~~~javascript function restricted() { 'use strict'; restricted.caller; // 報錯 restricted.arguments; // 報錯 } restricted(); ~~~ <br> ### 尾遞歸優化的實現 尾遞歸優化只在嚴格模式下生效,那么正常模式下,或者那些不支持該功能的環境中,有沒有辦法也使用尾遞歸優化呢?回答是可以的,就是自己實現尾遞歸優化。 <br> 它的原理非常簡單。尾遞歸之所以需要優化,原因是調用棧太多,造成溢出,那么只要減少調用棧,就不會溢出。怎么做可以減少調用棧呢?就是采用“循環”換掉“遞歸”。 <br> 下面是一個正常的遞歸函數。 ~~~javascript function sum(x, y) { if (y > 0) { return sum(x + 1, y - 1); } else { return x; } } sum(1, 100000) // Uncaught RangeError: Maximum call stack size exceeded(…) ~~~ <br> 上面代碼中,`sum`是一個遞歸函數,參數`x`是需要累加的值,參數`y`控制遞歸次數。一旦指定`sum`遞歸 100000 次,就會報錯,提示超出調用棧的最大次數。 <br> 蹦床函數(trampoline)可以將遞歸執行轉為循環執行。 ~~~javascript function trampoline(f) { while (f && f instanceof Function) { f = f(); } return f; } ~~~ <br> 上面就是蹦床函數的一個實現,它接受一個函數`f`作為參數。只要`f`執行后返回一個函數,就繼續執行。注意,這里是返回一個函數,然后執行該函數,而不是函數里面調用函數,這樣就避免了遞歸執行,從而就消除了調用棧過大的問題。 <br> 然后,要做的就是將原來的遞歸函數,改寫為每一步返回另一個函數。 ~~~javascript function sum(x, y) { if (y > 0) { return sum.bind(null, x + 1, y - 1); } else { return x; } } ~~~ <br> 上面代碼中,`sum`函數的每次執行,都會返回自身的另一個版本。 <br> 現在,使用蹦床函數執行`sum`,就不會發生調用棧溢出。 ~~~javascript trampoline(sum(1, 100000)) // 100001 ~~~ <br> 蹦床函數并不是真正的尾遞歸優化,下面的實現才是。 ~~~javascript function tco(f) { var value; var active = false; var accumulated = []; return function accumulator() { accumulated.push(arguments); if (!active) { active = true; while (accumulated.length) { value = f.apply(this, accumulated.shift()); } active = false; return value; } }; } var sum = tco(function(x, y) { if (y > 0) { return sum(x + 1, y - 1) } else { return x } }); sum(1, 100000) // 100001 ~~~ <br> 上面代碼中,`tco`函數是尾遞歸優化的實現,它的奧妙就在于狀態變量`active`。默認情況下,這個變量是不激活的。一旦進入尾遞歸優化的過程,這個變量就激活了。然后,每一輪遞歸`sum`返回的都是`undefined`,所以就避免了遞歸執行;而`accumulated`數組存放每一輪`sum`執行的參數,總是有值的,這就保證了`accumulator`函數內部的`while`循環總是會執行。這樣就很巧妙地將“遞歸”改成了“循環”,而后一輪的參數會取代前一輪的參數,保證了調用棧只有一層。 <br> ## 函子 函數不僅可以用于同一個范疇之中值的轉換,還可以用于將一個范疇轉成另一個范疇。這就涉及到了函子(Functor)。 <br> 函子是函數式編程里面最重要的數據類型,也是基本的運算單位和功能單位。 <br> 它首先是一種范疇,也就是說,是一個容器,包含了值和變形關系。比較特殊的是,它的變形關系可以依次作用于每一個值,將當前容器變形成另一個容器。 <br> `Functor `是一個對于函數調用的抽象,我們賦予容器自己去調用函數的能力。把東西裝進一個容器,只留出一個接口 `map `給容器外的函數,`map `一個函數時,我們讓容器自己來運行這個函數,這樣容器就可以自由地選擇何時何地如何操作這個函數,以致于擁有惰性求值、錯誤處理、異步調用等等非常的特性。 <br> 任何具有map方法的數據結構,都可以當作函子的實現。 ``` class Functor { constructor(val) { this.val = val; } map(f) { return new Functor(f(this.val)); } } ``` <br> 上面代碼中,`Functor`是一個函子,它的`map`方法接受函數f作為參數,然后返回一個新的函子,里面包含的值是被f處理過的(`f(this.val)`)。 <br> 一般約定,函子的標志就是容器具有map方法。該方法將容器里面的每一個值,映射到另一個容器。 <br> 下面是一些用法的示例。 ``` (new Functor(2)).map(function (two) { return two + 2; }); // Functor(4) (new Functor('flamethrowers')).map(function(s) { return s.toUpperCase(); }); // Functor('FLAMETHROWERS') (new Functor('bombs')).map(_.concat(' away')).map(_.prop('length')); // Functor(10) ``` <br> 上面的例子說明,函數式編程里面的運算,都是通過函子完成,即運算不直接針對值,而是針對這個值的容器----函子。函子本身具有對外接口(map方法),各種函數就是運算符,通過接口接入容器,引發容器里面的值的變形。 <br> ## of 方法 上面生成新的函子的時候,用了new命令。這實在太不像函數式編程了,因為new命令是面向對象編程的標志。函數式編程一般約定,函子有一個of方法,用來生成新的容器。 ``` Functor.of = function(val) { return new Functor(val); }; ``` <br> ## maybe函子 函子接受各種函數,處理容器內部的值。這里就有一個問題,容器內部的值可能是一個空值(比如null),而外部函數未必有處理空值的機制,如果傳入空值,很可能就會出錯。 <br> Maybe 函子就是為了解決這一類問題而設計的。簡單說,它的map方法里面設置了空值檢查。 ``` class Maybe { constructor(val) { this.val = val } static of(val) { // 生成新的容器 return new Maybe(val) } isNothing() { return (this.val === null || this.val === undefined) } map(f) { return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.val)) } } ``` <br> 有了 Maybe 函子,處理空值就不會出錯了。 ``` Maybe.of(null).map(function (s) { return s.toUpperCase(); }); // Maybe(null) ``` <br> ## Either 函子 條件運算if...else是最常見的運算之一,函數式編程里面,使用 Either 函子表達。 <br> Either 函子內部有兩個值:左值(Left)和右值(Right)。右值是正常情況下使用的值,左值是右值不存在時使用的默認值。 ``` class Either { constructor(left, right) { this.left = left this.right = right } static of(left, right) { return new Either(left, right) } map(f) { return this.right ? Either.of(this.left, f(this.right)) : Either.of(f(this.left), this.right) } } ``` <br> Either 函子的常見用途是提供默認值。下面是一個例子。 ``` Either .of({address: 'xxx'}, currentUser.address) .map(updateField); ``` 上面代碼中,如果用戶沒有提供地址,Either 函子就會使用左值的默認地址。 <br> Either 函子的另一個用途是代替try...catch,使用左值表示錯誤。 ``` class Left { constructor(val) { this.val = val } static of (x) { return new Left(x) } map (f) { return this } } class Right { constructor(val) { this.val = val } static of (x) { return new Left(x) } map (f) { return Right.of(f(this.val)) } } var getAge = user => user.age ? Right.of(user.age) : Left.of('ERROR!'); console.log(getAge({name: 'stark', age: '21'}).map(age => 'Age is ' + age)) // Left { val: '21' } console.log(getAge({name: 'stark'}).map(age => 'Age is ' + age)) // Left('ERROR!') ``` Left 可以讓調用鏈中任意一環的錯誤立刻返回到調用鏈的尾部,這給我們錯誤處理帶來了很大的方便,再也不用一層又一層的 try/catch。 <br> ## AP 函子 函子里面包含的值,完全可能是函數。我們可以想象這樣一種情況,一個函子的值是數值,另一個函子的值是函數。 ``` class Ap { constructor(val) { this.val = val } static of(val) { // 生成新的容器 return new Ap(val) } ap(F) { return Ap.of(this.val(F.val)); } } function addTwo(x) { return x + 2; } console.log(Ap.of(addTwo).ap(Ap.of(2))) ``` <br> ## Monad函子 Monad就是一種設計模式,表示將一個運算過程,通過函數拆解成互相連接的多個步驟。你只要提供下一步運算所需的函數,整個運算就會自動進行下去。 <br> Promise 就是一種 Monad。Monad 讓我們避開了嵌套地獄,可以輕松地進行深度嵌套的函數式編程,比如IO和其它異步任務。 <br> Monad 函子的作用是,總是返回一個單層的函子。它有一個flatMap方法,與map方法作用相同,唯一的區別是如果生成了一個嵌套函子,它會取出后者內部的值,保證返回的永遠是一個單層的容器,不會出現嵌套的情況。 <br> ``` class Monad extends Functor { join() { return this.val; } flatMap(f) { return this.map(f).join(); } } ``` 上面代碼中,如果函數f返回的是一個函子,那么this.map(f)就會生成一個嵌套的函子。所以,join方法保證了flatMap方法總是返回一個單層的函子。這意味著嵌套的函子會被鋪平(flatten)。 <br> ## IO 操作 Monad 函子的重要應用,就是實現 I/O (輸入輸出)操作。 <br> I/O 是不純的操作,普通的函數式編程沒法做,這時就需要把 IO 操作寫成Monad函子,通過它來完成。 <br> ``` var fs = require('fs'); var path = require('path') var _ = require('lodash'); var compose = _.flowRight; //基礎函子 class Functor { constructor(val) { this.val = val; } } //Monad 函子 class Monad extends Functor { join() { return this.val; } flatMap(f) { //1.f 接受一個函數返回的IO函子 //2.this.val 等于上一步的臟操作 //3.this.map(f) compose(f, this.val) 函數組合 需要手動執行 //4.返回這個組合函數并執行 注意先后的順序 return this.map(f).join(); } } //IO函子用來包裹臟操作 class IO extends Monad { //val是最初的臟操作 static of(val) { return new IO(val); } map(f) { return IO.of(compose(f, this.val)) } } var readFile = function(filename) { return new IO(function() { return fs.readFileSync(filename, 'utf-8'); }); }; var print = function(x) { return new IO(function() { console.log(x); return x; }); } ``` 上面代碼中,讀取文件和打印本身都是不純的操作,但是readFile和print卻是純函數,因為它們總是返回 IO 函子。 <br> 如果 IO 函子是一個Monad,具有flatMap方法,那么我們就可以像下面這樣調用這兩個函數。 ``` readFile('./user.txt') .flatMap(print) ``` <br> 這就是神奇的地方,上面的代碼完成了不純的操作,但是因為flatMap返回的還是一個 IO 函子,所以這個表達式是純的。我們通過一個純的表達式,完成帶有副作用的操作,這就是 Monad 的作用。 <br> 由于返回還是 IO 函子,所以可以實現鏈式操作。因此,在大多數庫里面,flatMap方法被改名成chain。 ``` var tail = function(x) { return new IO(function() { return x[x.length - 1]; }); } readFile('./user.txt') .flatMap(tail) .flatMap(print) // 等同于 readFile('./user.txt') .chain(tail) .chain(print) ``` 上面代碼讀取了文件user.txt,然后選取最后一行輸出。 <br> <br> # 流行的幾大函數式編程庫 * RxJS * cycleJS * lodashJS、lazy(惰性求值) * underscoreJS * ramdajs <br> # 實際應用場景 * 易調試、熱部署、并發 * 單元測試 <br> <br> # 參考資料 [函數式編程入門教程](http://www.ruanyifeng.com/blog/2017/02/fp-tutorial.html) [函數式編程術語](https://github.com/shfshanyue/fp-jargon-zh#monad) [函數式編程指南](https://legacy.gitbook.com/book/llh911001/mostly-adequate-guide-chinese) [ES6入門指南 - 尾調用優化](http://es6.ruanyifeng.com/#docs/function#尾調用優化) [Pointfree Javascript | Lucas Reis' Blog](https://lucasmreis.github.io/blog/pointfree-javascript/)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看