<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>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                [TOC] # Applicative Functor ## 應用 applicative functor 考慮到其函數式的出身,**applicative functor** 這個名稱堪稱簡單明了。函數式程序員最為人詬病的一點就是,總喜歡搞一些稀奇古怪的命名,比如 `mappend` 或者 `liftA4`。誠然,此類名稱出現在數學實驗室是再自然不過的,但是放在其他任何語境下,這些概念就都像是扮作達斯維達去汽車餐館搞怪的人。(譯者注:此處需要做些解釋,1. 汽車餐館(drive-thru)指的是那種不需要顧客下車就能提供服務的地方,比如麥當勞、星巴克等就會有這種 drive-thru;2. 達斯維達(Darth Vader)是《星球大戰》系列主要反派角色,在美國大眾文化中的有著廣泛的影響力,其造型是很多人致敬模仿的對象;3. 由于 2 的緣故,美國一些星戰迷會扮作 Darth Vader 去 drive-thru 點單,YouTube 上有不少這種[搞怪視頻](https://www.youtube.com/watch?v=eKVJZAMUh_A);4. 作者使用這個“典故”是為了說明函數式里很多概念的名稱有些“故弄玄虛”,而 applicative functor 是少數比較“正常”的。) 無論如何,applicative 這個名字應該能夠向我們表明一些事實,告訴我們作為一個接口,它能為我們帶來什么:那就是讓不同 functor 可以相互應用(apply)的能力。 然而,你可能為會問了,為何一個正常的、理性的人,比如你自己,會做這種“讓不同 functor 相互應用”的事?而且,“相互應用”到底*是什么意思*? 要回答這些問題,我們可以從下面這個場景講起,可能你已經碰到過這種場景了。假設有兩個同類型的 functor,我們想把這兩者作為一個函數的兩個參數傳遞過去來調用這個函數。簡單的例子比如讓兩個 `Container` 的值相加: ```js // 這樣是行不通的,因為 2 和 3 都藏在瓶子里。 add(Container.of(2), Container.of(3)); //NaN // 使用可靠的 map 函數試試 var container_of_add_2 = map(add, Container.of(2)); // Container(add(2)) ``` 這時候我們創建了一個 `Container`,它內部的值是一個局部調用的(partially applied)的函數。確切點講就是,我們想讓 `Container(add(2))` 中的 `add(2)` 應用到 `Container(3)` 中的 `3` 上來完成調用。也就是說,我們想把一個 functor 應用到另一個上。 巧的是,完成這種任務的工具已經存在了,即 `chain` 函數。我們可以先 `chain` 然后再 `map` 那個局部調用的 `add(2)`,就像這樣: ```js Container.of(2).chain(function(two) { return Container.of(3).map(add(two)); }); ``` 只不過,這種方式有一個問題,那就是 monad 的順序執行問題:所有的代碼都只會在前一個 monad 執行完畢之后才執行。想想看,我們的這兩個值足夠強健且相互獨立,如果僅僅為了滿足 monad 的順序要求而延遲 `Container(3)` 的創建,我覺得是非常沒有必要的。 事實上,當遇到這種問題的時候,要是能夠無需借助這些不必要的函數和變量,以一種簡明扼要的方式把一個 functor 的值應用到另一個上去就好了。 ## 瓶中之船 ![](https://box.kancloud.cn/5235ad9e8cd402367b72ffb03a786374_437x323.png) `ap` 就是這樣一種函數,能夠把一個 functor 的函數值應用到另一個 functor 的值上。把這句話快速地說上 5 遍。 ```js Container.of(add(2)).ap(Container.of(3)); // Container(5) // all together now Container.of(2).map(add).ap(Container.of(3)); // Container(5) ``` 這樣就大功告成了,而且代碼干凈整潔。可以看到,`Container(3)` 從嵌套的 monad 函數的牢籠中釋放了出來。需要再次強調的是,本例中的 `add` 是被 `map` 所局部調用(partially apply)的,所以 `add` 必須是一個 curry 函數。 可以這樣定義一個 `ap` 函數: ```js Container.prototype.ap = function(other_container) { return other_container.map(this.__value); } ``` 記住,`this.__value` 是一個函數,將會接收另一個 functor 作為參數,所以我們只需 `map` 它。由此我們可以得出 applicative functor 的定義: > applicative functor 是實現了 `ap` 方法的 pointed functor 注意 `pointed` 這個前提,這是非常重要的一個前提,下面的例子會說明這一點。 講到這里,我已經感受到你的疑慮了(也或者是困惑和恐懼);心態開放點嘛,`ap` 還是很有用的。在深入理解這個概念之前,我們先來探索一個特性。 ```js F.of(x).map(f) == F.of(f).ap(F.of(x)) ``` 這行代碼翻譯成人類語言就是,map 一個 `f` 等價于 `ap` 一個值為 `f` 的 functor。或者更好的譯法是,你既可以把 `x` 放到容器里然后調用 `map(f)`,也可以同時讓 `f` 和 `x` 發生 lift(參看第 8 章),然后對他們調用 `ap`。這讓我們能夠以一種從左到右的方式編寫代碼: ```js Maybe.of(add).ap(Maybe.of(2)).ap(Maybe.of(3)); // Maybe(5) Task.of(add).ap(Task.of(2)).ap(Task.of(3)); // Task(5) ``` 細心的讀者可能發現了,上述代碼中隱約有普通函數調用的影子。沒關系,我們稍后會學習 `ap` 的 pointfree 版本;暫時先把這當作此類代碼的推薦寫法。通過使用 `of`,每一個值都被輸送到了各個容器里的奇幻之地,就像是在另一個平行世界里,每個程序都可以是異步的或者是 null 或者隨便什么值,而且不管是什么,`ap` 都能在這個平行世界里針對這些值應用各種各樣的函數。這就像是在一個瓶子中造船。 你注意到沒?上例中我們使用了 `Task`,這是 applicative functor 主要的用武之地。現在我們來看一個更深入的例子。 ## 協調與激勵 假設我們要創建一個旅游網站,既需要獲取游客目的地的列表,還需要獲取地方事件的列表。這兩個請求就是相互獨立的 api 調用。 ```js // Http.get :: String -> Task Error HTML var renderPage = curry(function(destinations, events) { /* render page */ }); Task.of(renderPage).ap(Http.get('/destinations')).ap(Http.get('/events')) // Task("<div>some page with dest and events</div>") ``` 兩個請求將會同時立即執行,當兩者的響應都返回之后,`renderPage` 就會被調用。這與 monad 版本的那種必須等待前一個任務完成才能繼續執行后面的操作完全不同。本來我們就無需根據目的地來獲取事件,因此也就不需要依賴順序執行。 再次強調,因為我們是使用局部調用的函數來達成上述結果的,所以必須要保證 `renderpage` 是 curry 函數,否則它就不會一直等到兩個 `Task` 都完成。而且如果你碰巧自己做過類似的事,那你一定會感激 `applicative functor` 這個異常簡潔的接口的。這就是那種能夠讓我們離“奇點”(singularity)更近一步的優美代碼。 再來看另外一個例子。 ```js // 幫助函數: // ============== // $ :: String -> IO DOM var $ = function(selector) { return new IO(function(){ return document.querySelector(selector) }); } // getVal :: String -> IO String var getVal = compose(map(_.prop('value')), $); // Example: // =============== // signIn :: String -> String -> Bool -> User var signIn = curry(function(username, password, remember_me){ /* signing in */ }) IO.of(signIn).ap(getVal('#email')).ap(getVal('#password')).ap(IO.of(false)); // IO({id: 3, email: "gg@allin.com"}) ``` `signIn` 是一個接收 3 個參數的 curry 函數,因此我們需要調用 `ap` 3 次。在每一次的 `ap` 調用中,`signIn` 就收到一個參數然后運行,直到所有的參數都傳進來,它也就執行完畢了。我們可以繼續擴展這種模式,處理任意多的參數。另外,左邊兩個參數在使用 `getVal` 調用后自然而然地成為了一個 `IO`,但是最右邊的那個卻需要手動 `lift`,然后變成一個 `IO`,這是因為 `ap` 需要調用者及其參數都屬于同一類型。 ## lift (譯者注:此處原標題是“Bro, do you even lift?”,是一流行語,發源于健身圈,指質疑別人的健身方式和效果并顯示優越感,后擴散至其他領域。再注:作者書中用了不少此類俚語或俗語,有時并非在使用俚語的本意,就像這句,完全就是為了好玩。另,關于 lift 的概念可參看第 8 章。) 我們來試試以一種 pointfree 的方式調用 applicative functor。因為 `map` 等價于 `of/ap`,那么我們就可以定義無數個能夠 `ap` 通用函數。 ```js var liftA2 = curry(function(f, functor1, functor2) { return functor1.map(f).ap(functor2); }); var liftA3 = curry(function(f, functor1, functor2, functor3) { return functor1.map(f).ap(functor2).ap(functor3); }); //liftA4, etc ``` `liftA2` 是個奇怪的名字,聽起來像是破敗工廠里挑剔的貨運電梯,或者偽豪華汽車公司的個性車牌。不過你要是真正理解了,那么它的含義也就不證自明了:讓那些小代碼塊發生 lift,成為 applicative functor 中的一員。 剛開始我也覺得這種 2-3-4 的寫法沒什么意義,看起來又丑又沒有必要,畢竟我們可以在 JavaScript 中檢查函數的參數數量然后再動態地構造這樣的函數。不過,局部調用(partially apply)`liftA(N)` 本身,有時也能發揮它的用處,這樣的話,參數數量就固定了。 來看看實際用例: ```js // checkEmail :: User -> Either String Email // checkName :: User -> Either String String // createUser :: Email -> String -> IO User var createUser = curry(function(email, name) { /* creating... */ }); Either.of(createUser).ap(checkEmail(user)).ap(checkName(user)); // Left("invalid email") liftA2(createUser, checkEmail(user), checkName(user)); // Left("invalid email") ``` `createUser` 接收兩個參數,因此我們使用的是 `liftA2`。上述兩個語句是等價的,但是使用了 `liftA2` 的版本沒有提到 `Either`,這就使得它更加通用靈活,因為不必與特定的數據類型耦合在一起。 我們試試以這種方式重寫前一個例子: ```js liftA2(add, Maybe.of(2), Maybe.of(3)); // Maybe(5) liftA2(renderPage, Http.get('/destinations'), Http.get('/events')) // Task("<div>some page with dest and events</div>") liftA3(signIn, getVal('#email'), getVal('#password'), IO.of(false)); // IO({id: 3, email: "gg@allin.com"}) ``` ## 操作符 在 haskell、scala、PureScript 以及 swift 等語言中,開發者可以創建自定義的中綴操作符(infix operators),所以你能看到到這樣的語法: ```hs -- haskell add <$> Right 2 <*> Right 3 ``` ```js // JavaScript map(add, Right(2)).ap(Right(3)) ``` `<$>` 就是 `map`(亦即 `fmap`),`<*>` 不過就是 `ap`。這樣的語法使得開發者可以以一種更自然的風格來書寫函數式應用,而且也能減少一些括號。 # 免費開瓶器 ![canopener](https://box.kancloud.cn/676fe1daa471c696a28a4ebdc4440d83_568x385.png) 我們尚未對衍生函數(derived function)著墨過多。不過看到本書介紹的所有這些接口都互相依賴并遵守一些定律,那么我們就可以根據一些強接口來定義一些弱接口了。 比如,我們知道一個 applicative 首先是一個 functor,所以如果已經有一個 applicative 實例的話,毫無疑問可以依此定義一個 functor。 這種完美的計算上的大和諧(computational harmony)之所以存在,是因為我們在跟一個數學“框架”打交道。哪怕是莫扎特在小時候就下載了 ableton(譯者注:一款專業的音樂制作軟件),他的鋼琴也不可能彈得更好。 前面提到過,`of/ap` 等價于 `map`,那么我們就可以利用這點來定義 `map`: ```js // 從 of/ap 衍生出的 map X.prototype.map = function(f) { return this.constructor.of(f).ap(this); } ``` monad 可以說是處在食物鏈的頂端,因此如果已經有了一個 `chain` 函數,那么就可以免費得到 functor 和 applicative: ```js // 從 chain 衍生出的 map X.prototype.map = function(f) { var m = this; return m.chain(function(a) { return m.constructor.of(f(a)); }); } // 從 chain/map 衍生出的 ap X.prototype.ap = function(other) { return this.chain(function(f) { return other.map(f); }); }; ``` 定義一個 monad,就既能得到 applicative 也能得到 functor。這一點非常強大,相當于這些“開瓶器”全都是免費的!我們甚至可以審查一個數據類型,然后自動化這個過程。 應該要指出來的一點是,`ap` 的魅力有一部分就來自于并行的能力,所以通過 `chain` 來定義它就失去了這種優化。即便如此,開發者在設計出最佳實現的過程中就能有一個立即可用的接口,也是很好的。 為啥不直接使用 monad?因為最好用合適的力量來解決合適的問題,一分不多,一分不少。這樣就能通過排除可能的功能性來做到最小化認知負荷。因為這個原因,相比 monad,我們更傾向于使用 applicative。 向下的嵌套結構使得 monad 擁有串行計算、變量賦值和暫緩后續執行等獨特的能力。不過見識到 applicative 的實際用例之后,你就不必再考慮上面這些問題了。 下面,來看看理論知識。 ## 定律 就像我們探索過的其他數學結構一樣,我們在日常編碼中也依賴 applicative functor 一些有用的特性。首先,你應該知道 applicative functor 是“組合關閉”(closed under composition)的,意味著 `ap` 永遠不會改變容器類型(另一個勝過 monad 的原因)。這并不是說我們無法擁有多種不同的作用——我們還是可以把不同的類型壓棧的,只不過我們知道它們將會在整個應用的過程中保持不變。 下面的例子可以說明這一點: ```js var tOfM = compose(Task.of, Maybe.of); liftA2(_.concat, tOfM('Rainy Days and Mondays'), tOfM(' always get me down')); // Task(Maybe(Rainy Days and Mondays always get me down)) ``` 你看,不必擔心不同的類型會混合在一起。 該去看看我們最喜歡的范疇學定律了:*同一律*(identity)。 ### 同一律(identity) ```js // 同一律 A.of(id).ap(v) == v ``` 是的,對一個 functor 應用 `id` 函數不會改變 `v` 里的值。比如: ```js var v = Identity.of("Pillow Pets"); Identity.of(id).ap(v) == v ``` `Identity.of(id)` 的“無用性”讓我不禁莞爾。這里有意思的一點是,就像我們之前證明了的,`of/ap` 等價于 `map`,因此這個同一律遵循的是 functor 的同一律:`map(id) == id`。 使用這些定律的優美之處在于,就像一個富有激情的幼兒園健身教練讓所有的小朋友都能愉快地一塊玩耍一樣,它們能夠強迫所有的接口都能完美結合。 ### 同態(homomorphism) ```js // 同態 A.of(f).ap(A.of(x)) == A.of(f(x)) ``` *同態*就是一個能夠保持結構的映射(structure preserving map)。實際上,functor 就是一個在不同范疇間的同態,因為 functor 在經過映射之后保持了原始范疇的結構。 事實上,我們不過是把普通的函數和值放進了一個容器,然后在里面進行各種計算。所以,不管是把所有的計算都放在容器里(等式左邊),還是先在外面進行計算然后再放到容器里(等式右邊),其結果都是一樣的。 一個簡單例子: ```js Either.of(_.toUpper).ap(Either.of("oreos")) == Either.of(_.toUpper("oreos")) ``` ### 互換(interchange) 互換(interchange)表明的是選擇讓函數在 `ap` 的左邊還是右邊發生 lift 是無關緊要的。 ```js // 互換 v.ap(A.of(x)) == A.of(function(f) { return f(x) }).ap(v) ``` 這里有個例子: ```js var v = Task.of(_.reverse); var x = 'Sparklehorse'; v.ap(Task.of(x)) == Task.of(function(f) { return f(x) }).ap(v) ``` ### 組合(composition) 最后是組合。組合不過是在檢查標準的函數組合是否適用于容器內部的函數調用。 ```js // 組合 A.of(compose).ap(u).ap(v).ap(w) == u.ap(v.ap(w)); ``` ```js var u = IO.of(_.toUpper); var v = IO.of(_.concat("& beyond")); var w = IO.of("blood bath "); IO.of(_.compose).ap(u).ap(v).ap(w) == u.ap(v.ap(w)) ``` ## 總結 處理多個 functor 作為參數的情況,是 applicative functor 一個非常好的應用場景。借助 applicative functor,我們能夠在 functor 的世界里調用函數。盡管已經可以通過 monad 達到這個目的,但在不需要 monad 的特定功能的時候,我們還是更傾向于使用 applicative functor。 至此我們已經基本介紹完容器的 api 了,我們學會了如何對函數調用 `map`、`chain` 和 `ap`。下一章,我們將學習如何更好地處理多個 functor,以及如何以一種原則性的方式拆解它們。 [Chapter 11: Traversable/Foldable Functors](ch11.md) ## 練習 ```js require('./support'); var Task = require('data.task'); var _ = require('ramda'); // 模擬瀏覽器的 localStorage 對象 var localStorage = {}; // 練習 1 // ========== // 寫一個函數,使用 Maybe 和 ap() 實現讓兩個可能是 null 的數值相加。 // ex1 :: Number -> Number -> Maybe Number var ex1 = function(x, y) { }; // 練習 2 // ========== // 寫一個函數,接收兩個 Maybe 為參數,讓它們相加。使用 liftA2 代替 ap()。 // ex2 :: Maybe Number -> Maybe Number -> Maybe Number var ex2 = undefined; // 練習 3 // ========== // 運行 getPost(n) 和 getComments(n),兩者都運行完畢后執行渲染頁面的操作。(參數 n 可以是任意值)。 var makeComments = _.reduce(function(acc, c){ return acc+"<li>"+c+"</li>" }, ""); var render = _.curry(function(p, cs) { return "<div>"+p.title+"</div>"+makeComments(cs); }); // ex3 :: Task Error HTML var ex3 = undefined; // 練習 4 // ========== // 寫一個 IO,從緩存中讀取 player1 和 player2,然后開始游戲。 localStorage.player1 = "toby"; localStorage.player2 = "sally"; var getCache = function(x) { return new IO(function() { return localStorage[x]; }); } var game = _.curry(function(p1, p2) { return p1 + ' vs ' + p2; }); // ex4 :: IO String var ex4 = undefined; // 幫助函數 // ===================== function getPost(i) { return new Task(function (rej, res) { setTimeout(function () { res({ id: i, title: 'Love them futures' }); }, 300); }); } function getComments(i) { return new Task(function (rej, res) { setTimeout(function () { res(["This book should be illegal", "Monads are like space burritos"]); }, 300); }); } ```
                  <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>

                              哎呀哎呀视频在线观看