<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] # 第 5 章: 代碼組合(compose) ## 函數飼養 這就是 `組合`(compose,以下將稱之為組合): ```js var compose = function(f,g) { return function(x) { return f(g(x)); }; }; ``` `f` 和 `g` 都是函數,`x` 是在它們之間通過“管道”傳輸的值。 `組合`看起來像是在飼養函數。你就是飼養員,選擇兩個有特點又遭你喜歡的函數,讓它們結合,產下一個嶄新的函數。組合的用法如下: ```js var toUpperCase = function(x) { return x.toUpperCase(); }; var exclaim = function(x) { return x + '!'; }; var shout = compose(exclaim, toUpperCase); shout("send in the clowns"); //=> "SEND IN THE CLOWNS!" ``` 兩個函數組合之后返回了一個新函數是完全講得通的:組合某種類型(本例中是函數)的兩個元素本就該生成一個該類型的新元素。把兩個樂高積木組合起來絕不可能得到一個林肯積木。所以這是有道理的,我們將在適當的時候探討這方面的一些底層理論。 在 `compose` 的定義中,`g` 將先于 `f` 執行,因此就創建了一個從右到左的數據流。這樣做的可讀性遠遠高于嵌套一大堆的函數調用,如果不用組合,`shout` 函數將會是這樣的: ```js var shout = function(x){ return exclaim(toUpperCase(x)); }; ``` 讓代碼從右向左運行,而不是由內而外運行,我覺得可以稱之為“左傾”(吁——)。我們來看一個順序很重要的例子: ```js var head = function(x) { return x[0]; }; var reverse = reduce(function(acc, x){ return [x].concat(acc); }, []); var last = compose(head, reverse); last(['jumpkick', 'roundhouse', 'uppercut']); //=> 'uppercut' ``` `reverse` 反轉列表,`head` 取列表中的第一個元素;所以結果就是得到了一個 `last` 函數(譯者注:即取列表的最后一個元素),雖然它性能不高。這個組合中函數的執行順序應該是顯而易見的。盡管我們可以定義一個從左向右的版本,但是從右向左執行更加能夠反映數學上的含義——是的,組合的概念直接來自于數學課本。實際上,現在是時候去看看所有的組合都有的一個特性了。 ```js // 結合律(associativity) var associative = compose(f, compose(g, h)) == compose(compose(f, g), h); // true ``` 這個特性就是結合律,符合結合律意味著不管你是把 `g` 和 `h` 分到一組,還是把 `f` 和 `g` 分到一組都不重要。所以,如果我們想把字符串變為大寫,可以這么寫: ```js compose(toUpperCase, compose(head, reverse)); // 或者 compose(compose(toUpperCase, head), reverse); ``` 因為如何為 `compose` 的調用分組不重要,所以結果都是一樣的。這也讓我們有能力寫一個可變的組合(variadic compose),用法如下: ```js // 前面的例子中我們必須要寫兩個組合才行,但既然組合是符合結合律的,我們就可以只寫一個, // 而且想傳給它多少個函數就傳給它多少個,然后讓它自己決定如何分組。 var lastUpper = compose(toUpperCase, head, reverse); lastUpper(['jumpkick', 'roundhouse', 'uppercut']); //=> 'UPPERCUT' var loudLastUpper = compose(exclaim, toUpperCase, head, reverse) loudLastUpper(['jumpkick', 'roundhouse', 'uppercut']); //=> 'UPPERCUT!' ``` 運用結合律能為我們帶來強大的靈活性,還有對執行結果不會出現意外的那種平和心態。至于稍微復雜些的可變組合,也都包含在本書的 `support` 庫里了,而且你也可以在類似 [lodash][lodash-website]、[underscore][underscore-website] 以及 [ramda][ramda-website] 這樣的類庫中找到它們的常規定義。 結合律的一大好處是任何一個函數分組都可以被拆開來,然后再以它們自己的組合方式打包在一起。讓我們來重構重構前面的例子: ```js var loudLastUpper = compose(exclaim, toUpperCase, head, reverse); // 或 var last = compose(head, reverse); var loudLastUpper = compose(exclaim, toUpperCase, last); // 或 var last = compose(head, reverse); var angry = compose(exclaim, toUpperCase); var loudLastUpper = compose(angry, last); // 更多變種... ``` 關于如何組合,并沒有標準的答案——我們只是以自己喜歡的方式搭樂高積木罷了。通常來說,最佳實踐是讓組合可重用,就像 `last` 和 `angry` 那樣。如果熟悉 Fowler 的《[重構][refactoring-book]》一書的話,你可能會認識到這個過程叫做 “[extract method][extract-method-refactor]”——只不過不需要關心對象的狀態。 ## pointfree pointfree 模式指的是,永遠不必說出你的數據。咳咳對不起(譯者注:此處原文是“Pointfree style means never having to say your data”,源自 1970 年的電影 *Love Story* 里的一句著名臺詞“Love means never having to say you're sorry”。緊接著作者又說了一句“Excuse me”,大概是一種幽默)。它的意思是說,函數無須提及將要操作的數據是什么樣的。一等公民的函數、柯里化(curry)以及組合協作起來非常有助于實現這種模式。 ```js // 非 pointfree,因為提到了數據:word var snakeCase = function (word) { return word.toLowerCase().replace(/\s+/ig, '_'); }; // pointfree var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase); ``` 看到 `replace` 是如何被局部調用的了么?這里所做的事情就是通過管道把數據在接受單個參數的函數間傳遞。利用 curry,我們能夠做到讓每個函數都先接收數據,然后操作數據,最后再把數據傳遞到下一個函數那里去。另外注意在 pointfree 版本中,不需要 `word` 參數就能構造函數;而在非 pointfree 的版本中,必須要有 `word` 才能進行進行一切操作。 我們再來看一個例子。 ```js // 非 pointfree,因為提到了數據:name var initials = function (name) { return name.split(' ').map(compose(toUpperCase, head)).join('. '); }; // pointfree var initials = compose(join('. '), map(compose(toUpperCase, head)), split(' ')); initials("hunter stockton thompson"); // 'H. S. T' ``` 另外,pointfree 模式能夠幫助我們減少不必要的命名,讓代碼保持簡潔和通用。對函數式代碼來說,pointfree 是非常好的石蕊試驗,因為它能告訴我們一個函數是否是接受輸入返回輸出的小函數。比如,while 循環是不能組合的。不過你也要警惕,pointfree 就像是一把雙刃劍,有時候也能混淆視聽。并非所有的函數式代碼都是 pointfree 的,不過這沒關系。可以使用它的時候就使用,不能使用的時候就用普通函數。 ## debug 組合的一個常見錯誤是,在沒有局部調用之前,就組合類似 `map` 這樣接受兩個參數的函數。 ```js // 錯誤做法:我們傳給了 `angry` 一個數組,根本不知道最后傳給 `map` 的是什么東西。 var latin = compose(map, angry, reverse); latin(["frog", "eyes"]); // error // 正確做法:每個函數都接受一個實際參數。 var latin = compose(map(angry), reverse); latin(["frog", "eyes"]); // ["EYES!", "FROG!"]) ``` 如果在 debug 組合的時候遇到了困難,那么可以使用下面這個實用的,但是不純的 `trace` 函數來追蹤代碼的執行情況。 ```js var trace = curry(function(tag, x){ console.log(tag, x); return x; }); var dasherize = compose(join('-'), toLower, split(' '), replace(/\s{2,}/ig, ' ')); dasherize('The world is a vampire'); // TypeError: Cannot read property 'apply' of undefined ``` 這里報錯了,來 `trace` 下: ```js var dasherize = compose(join('-'), toLower, trace("after split"), split(' '), replace(/\s{2,}/ig, ' ')); // after split [ 'The', 'world', 'is', 'a', 'vampire' ] ``` 啊!`toLower` 的參數是一個數組,所以需要先用 `map` 調用一下它。 ```js var dasherize = compose(join('-'), map(toLower), split(' '), replace(/\s{2,}/ig, ' ')); dasherize('The world is a vampire'); // 'the-world-is-a-vampire' ``` `trace` 函數允許我們在某個特定的點觀察數據以便 debug。像 haskell 和 purescript 之類的語言出于開發的方便,也都提供了類似的函數。 組合將成為我們構造程序的工具,而且幸運的是,它背后是有一個強大的理論做支撐的。讓我們來研究研究這個理論。 ## 范疇學 范疇學(category theory)是數學中的一個抽象分支,能夠形式化諸如集合論(set theory)、類型論(type theory)、群論(group theory)以及邏輯學(logic)等數學分支中的一些概念。范疇學主要處理對象(object)、態射(morphism)和變化式(transformation),而這些概念跟編程的聯系非常緊密。下圖是一些相同的概念分別在不同理論下的形式: ![cat_theory](https://box.kancloud.cn/fdafa2ad2966b8227bc93c9644c1e799_687x399.png) 抱歉,我沒有任何要嚇唬你的意思。我并不假設你對這些概念都了如指掌,我只是想讓你明白這里面有多少重復的內容,讓你知道為何范疇學要統一這些概念。 在范疇學中,有一個概念叫做...范疇。有著以下這些組件(component)的搜集(collection)就構成了一個范疇: * 對象的搜集 * 態射的搜集 * 態射的組合 * identity 這個獨特的態射 范疇學抽象到足以模擬任何事物,不過目前我們最關心的還是類型和函數,所以讓我們把范疇學運用到它們身上看看。 **對象的搜集** 對象就是數據類型,例如 `String`、`Boolean`、`Number` 和 `Object` 等等。通常我們把數據類型視作所有可能的值的一個集合(set)。像 `Boolean` 就可以看作是 `[true, false]` 的集合,`Number` 可以是所有實數的一個集合。把類型當作集合對待是有好處的,因為我們可以利用集合論(set theory)處理類型。 **態射的搜集** 態射是標準的、普通的純函數。 **態射的組合** 你可能猜到了,這就是本章介紹的新玩意兒——`組合`。我們已經討論過 `compose` 函數是符合結合律的,這并非巧合,結合律是在范疇學中對任何組合都適用的一個特性。 這張圖展示了什么是組合: ![cat_comp1](https://box.kancloud.cn/d5464c79e81dee40bee56088691426e7_820x228.png) ![cat_comp2](https://box.kancloud.cn/d6cc7376aa63d17bea4aee60e6bacb6a_802x227.png) 這里有一個具體的例子: ```js var g = function(x){ return x.length; }; var f = function(x){ return x === 4; }; var isFourLetterWord = compose(f, g); ``` **identity 這個獨特的態射** 讓我們介紹一個名為 `id` 的實用函數。這個函數接受隨便什么輸入然后原封不動地返回它: ```js var id = function(x){ return x; }; ``` 你可能會問“這到底哪里有用了?”。別急,我們會在隨后的章節中拓展這個函數的,暫時先把它當作一個可以替代給定值的函數——一個假裝自己是普通數據的函數。 `id` 函數跟組合一起使用簡直完美。下面這個特性對所有的一元函數(unary function)(一元函數:只接受一個參數的函數) `f` 都成立: ```js // identity compose(id, f) == compose(f, id) == f; // true ``` 嘿,這就是實數的單位元(identity property)嘛!如果這還不夠清楚直白,別著急,慢慢理解它的無用性。很快我們就會到處使用 `id` 了,不過暫時我們還是把當作一個替代給定值的函數。這對寫 pointfree 的代碼非常有用。 好了,以上就是類型和函數的范疇。不過如果你是第一次聽說這些概念,我估計你還是有些迷糊,不知道范疇到底是什么,為什么有用。沒關系,本書全書都在借助這些知識編寫示例代碼。至于現在,就在本章,本行文字中,你至少可以認為它向我們提供了有關組合的知識——比如結合律和單位律。 除了類型和函數,還有什么范疇呢?還有很多,比如我們可以定義一個有向圖(directed graph),以節點為對象,以邊為態射,以路徑連接為組合。還可以定義一個實數類型(Number),以所有的實數對象,以 `>=` 為態射(實際上任何偏序(partial order)或全序(total order)都可以成為一個范疇)。范疇的總數是無限的,但是要達到本書的目的,我們只需要關心上面定義的范疇就好了。至此我們已經大致瀏覽了一些表面的東西,必須要繼續后面的內容了。 ## 總結 組合像一系列管道那樣把不同的函數聯系在一起,數據就可以也必須在其中流動——畢竟純函數就是輸入對輸出,所以打破這個鏈條就是不尊重輸出,就會讓我們的應用一無是處。 我們認為組合是高于其他所有原則的設計原則,這是因為組合讓我們的代碼簡單而富有可讀性。另外范疇學將在應用架構、模擬副作用和保證正確性方面扮演重要角色。 現在我們已經有足夠的知識去進行一些實際的練習了,讓我們來編寫一個示例應用。 [第 6 章: 示例應用](ch6.md) ## 練習 ```js require('../../support'); var _ = require('ramda'); var accounting = require('accounting'); // 示例數據 var CARS = [ {name: "Ferrari FF", horsepower: 660, dollar_value: 700000, in_stock: true}, {name: "Spyker C12 Zagato", horsepower: 650, dollar_value: 648000, in_stock: false}, {name: "Jaguar XKR-S", horsepower: 550, dollar_value: 132000, in_stock: false}, {name: "Audi R8", horsepower: 525, dollar_value: 114200, in_stock: false}, {name: "Aston Martin One-77", horsepower: 750, dollar_value: 1850000, in_stock: true}, {name: "Pagani Huayra", horsepower: 700, dollar_value: 1300000, in_stock: false} ]; // 練習 1: // ============ // 使用 _.compose() 重寫下面這個函數。提示:_.prop() 是 curry 函數 var isLastInStock = function(cars) { var last_car = _.last(cars); return _.prop('in_stock', last_car); }; // 練習 2: // ============ // 使用 _.compose()、_.prop() 和 _.head() 獲取第一個 car 的 name var nameOfFirstCar = undefined; // 練習 3: // ============ // 使用幫助函數 _average 重構 averageDollarValue 使之成為一個組合 var _average = function(xs) { return reduce(add, 0, xs) / xs.length; }; // <- 無須改動 var averageDollarValue = function(cars) { var dollar_values = map(function(c) { return c.dollar_value; }, cars); return _average(dollar_values); }; // 練習 4: // ============ // 使用 compose 寫一個 sanitizeNames() 函數,返回一個下劃線連接的小寫字符串:例如:sanitizeNames(["Hello World"]) //=> ["hello_world"]。 var _underscore = replace(/\W+/g, '_'); //<-- 無須改動,并在 sanitizeNames 中使用它 var sanitizeNames = undefined; // 彩蛋 1: // ============ // 使用 compose 重構 availablePrices var availablePrices = function(cars) { var available_cars = _.filter(_.prop('in_stock'), cars); return available_cars.map(function(x){ return accounting.formatMoney(x.dollar_value); }).join(', '); }; // 彩蛋 2: // ============ // 重構使之成為 pointfree 函數。提示:可以使用 _.flip() var fastestCar = function(cars) { var sorted = _.sortBy(function(car){ return car.horsepower }, cars); var fastest = _.last(sorted); return fastest.name + ' is the fastest'; }; ``` [lodash-website]: https://lodash.com/ [underscore-website]: http://underscorejs.org/ [ramda-website]: http://ramdajs.com/ [refactoring-book]: http://martinfowler.com/books/refactoring.html [extract-method-refactor]: http://refactoring.com/catalog/extractMethod.html
                  <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>

                              哎呀哎呀视频在线观看