摘要:就像我寫書的過程一樣,每個開發者在學習函數式編程的旅程中都會經歷這個部分。類型在函數式編程中有一個巨大的興趣領域類型論,本書基本上完全遠離了該領域。在函數式編程中,像這樣涵蓋是很普遍的。
原文地址:Functional-Light-JS
原文作者:Kyle Simpson-《You-Dont-Know-JS》作者
JavaScript 輕量級函數式編程 附錄 B: 謙虛的 Monad關于譯者:這是一個流淌著滬江血液的純粹工程:認真,是 HTML 最堅實的梁柱;分享,是 CSS 里最閃耀的一瞥;總結,是 JavaScript 中最嚴謹的邏輯。經過捶打磨練,成就了本書的中文版。本書包含了函數式編程之精髓,希望可以幫助大家在學習函數式編程的道路上走的更順暢。比心。
譯者團隊(排名不分先后):阿希、blueken、brucecham、cfanlife、dail、kyoko-df、l3ve、lilins、LittlePineapple、MatildaJin、冬青、pobusama、Cherry、蘿卜、vavd317、vivaxy、萌萌、zhouyao
首先,我坦白:在開始寫以下內容之前我并不太了解 Monad 是什么。我為了確認一些事情而犯了很多錯誤。如果你不相信我,去看看 這本書 Git 倉庫 中關于本章的提交歷史吧!
我在本書中囊括了所有涉及 Monad 的話題。就像我寫書的過程一樣,每個開發者在學習函數式編程的旅程中都會經歷這個部分。
盡管其他函數式編程的著作差不多都把 Monad 作為開始,而我們卻只對它做了簡要說明,并基本以此結束本書。在輕量級函數式編程中我確實沒有遇到太多需要仔細考慮 Monad 的問題,這就是本文更有價值的原因。但是并不是說 Monad 是沒用的或者是不普遍的 —— 恰恰相反,它很有用,也很流行。
函數式編程界有一個小笑話,幾乎每個人都不得不在他們的文章或者博客里寫 Monad 是什么,把它拎出來寫就像是一個儀式。在過去的幾年里,人們把 Monad 描述為卷餅、洋蔥和各種各樣古怪的抽象概念。我肯定不會重蹈覆轍!
一個 Monad 僅僅是自函子 (endofunctor) 范疇中的一個 monoid
我們引用這句話來開場,所以把話題轉到這個引言上面似乎是很合適的。可是才不會這樣,我們不會討論 Monad 、endofunctor 或者范疇論。這句引言不僅故弄玄虛而且華而不實。
我只希望通過我們的討論,你不再害怕 Monad 這個術語或者這個概念了 —— 我曾經怕了很長一段時間 —— 并在看到該術語時知道它是什么。你可能,也只是可能,會正確地使用到它們。
類型在函數式編程中有一個巨大的興趣領域:類型論,本書基本上完全遠離了該領域。我不會深入到類型論,坦白的說,我沒有深入的能力,即使干了也吃力不討好。
但是我要說,Monad 基本上是一個值類型。
數字 42 有一個值類型(number),它帶有我們依賴的特征和功能。字符串 "42" 可能看起來很像,但是在編程里它有不同的用途。
在面向對象編程中,當你有一組數據(甚至是一個多帶帶的離散值),并且想要給它綁上一些行為,那么你將創建一個對象或者類來表示 "type"。接著實例就成了該類型的一員。這種做法通常被稱為 “數據結構”。
我將會非常寬泛的使用數據結構這個概念,而且我斷定,當我們在編程中為一個特定的值定義一組行為以及約束條件,并且將這些特征與值一起綁定在一個單一抽象概念上時,我們可能會覺得很有用。這樣,當我們在編程中使用一個或多個這種值的時候,它們的行為會自然的出現,并且會使它們更方便的工作。方便的是,對你的代碼的讀者來說,是更有描述性和聲明性的。
Monad 是一種數據結構。是一種類型。它是一組使處理某個值變得可預測的特定行為。
回顧第 8 章,我們談到了函子(functor):包括一個值和一個用來對構成函子的數據執行操作的類 map 實用函數。Monad 是一個包含一些額外行為的函子(functor)。
松散接口實際上,Monad 并不是單一的數據類型,它更像是相關聯的數據類型集合。它是一種根據不同值的需要而用不同方式實現的接口。每種實現都是一種不同類型的 Monad。
例如,你可能閱讀 "Identity Monad"、"IO Monad"、"Maybe Monad"、"Either Monad" 或其他形形色色的字眼。他們中的每一個都有基本的 Monad 行為定義,但是它根據每個不同類型的 Monad 用例來繼承或者重寫交互行為。
可是它不僅僅是一個接口,因為它不只是使對象成為 Monad 的某些 API 方法的實現。對這些方法的交互的保障是必須的,是 monadic 的。這些眾所周知的常量對于使用 Monad 提高可讀性是至關重要的;另外,它是一個特殊的數據結構,讀者必須全部閱讀才能明白。
事實上,這些 Monad 方法的名字和真實接口授權的方式甚至沒有一個統一的標準;Monad 更像是一個松散接口。有些人稱這些方法為 bind(..),有些稱它為 chain(..),還有些稱它為 flatMap(..),等等。
所以,Monad 是一個對象數據結構,并且有充足的方法(幾乎任何名稱或排序),至少滿足了 Monad 定義的主要行為需求。每一種 Monad 都基于最少數量的方法來進行不同的擴展。但是,因為它們在行為上都有重疊,所以一起使用兩種不同的 Monad 仍然是直截了當和可控的。
從某種意義上說,Monad 更像是接口。
Maybe在函數式編程中,像 Maybe 這樣涵蓋 Monad 是很普遍的。事實上,Maybe Monad 是另外兩個更簡單的 Monad 的搭配:Just 和 Nothing。
既然 Monad 是一個類型,你可能認為我們應該定義 Maybe 作為一個要被實例化的類。這雖然是一種有效的方法,但是它引入了 this 綁定的問題,所以在這里我不想討論;相反,我打算使用一個簡單的函數和對象的實現方式。
以下是 Maybe 的最簡單的實現:
var Maybe = { Just, Nothing, of/* 又稱:unit,pure */: Just }; function Just(val) { return { map, chain, ap, inspect }; // ********************* function map(fn) { return Just( fn( val ) ); } // 又稱:bind, flatMap function chain(fn) { return fn( val ); } function ap(anotherMonad) { return anotherMonad.map( val ); } function inspect() { return `Just(${ val })`; } } function Nothing() { return { map: Nothing, chain: Nothing, ap: Nothing, inspect }; // ********************* function inspect() { return "Nothing"; } }
注意: inspect(..) 方法只用于我們的示例中。從 Monad 的角度來說,它并沒有任何意義。
如果現在大部分都沒有意義的話,不要擔心。我們將會更專注的說明我們可以用它做什么,而不是過多的深入 Monad 背后的設計細節和理論。
所有的 Monad 一樣,任何含有 Just(..) 和 Nothing() 的 Monad 實例都有 map(..)、chain(..)(也叫 bind(..) 或者 flatMap(..))和 ap(..) 方法。這些方法及其行為的目的在于提供多個 Monad 實例一起工作的標準化方法。你將會注意到,無論 Just(..) 實例拿到的是怎樣的一個 val 值, Just(..) 實例都不會去改變它。所有的方法都會創建一個新的 Monad 實例而不是改變它。
Maybe 是這兩個 Monad 的結合。如果一個值是非空的,它是 Just(..) 的實例;如果該值是空的,它則是 Nothing() 的實例。注意,這里由你的代碼來決定 "空" 的意思,我們不做強制限制。下一節會詳細介紹這一點。
但是 Monad 的價值在于不論我們有 Just(..) 實例還是 Nothing() 實例,我們使用的方法都是一樣的。Nothing() 實例對所有的方法都有空操作定義。所以如果 Monad 實例出現在 Monad 操作中,它就會對 Monad 操作起短路(short-circuiting)作用。
Maybe 這個抽象概念的作用是隱式地封裝了操作和無操作的二元性。
與眾不同的 MaybeJavaScript Maybe Monad 的許多實現都包含 null 和 undefined 的檢查(通常在 map(..)中),如果是空的話,就跳過該 Monad 的特性行為。事實上,Maybe 被聲稱是有價值的,因為它自動地封裝了空值檢查得以在某種程度上短路了它的特性行為。
這是 Maybe 的典型說明:
// 代替不穩定的 `console.log( someObj.something.else.entirely )`: Maybe.of( someObj ) .map( prop( "something" ) ) .map( prop( "else" ) ) .map( prop( "entirely" ) ) .map( console.log );
換句話說,如果我們在鏈式操作中的任何一環得到一個 null 或者 undefined 值,Maybe 會智能的切換到空操作模式 —— 它現在是一個 Nothing() Monad 實例! —— 把剩余的鏈式操作都停止掉。如果一些屬性丟失或者是空的話,嵌套的屬性訪問能安全的拋出 JS 異常。這是非常酷的而且很實用。
但是,我們這樣實現的 Maybe 不是一個純 Monad。
Monad 的核心思想是,它必須對所有的值都是有效的,不能對值做任何檢查 —— 甚至是空值檢查。所以為了方便,這些其他的實現都是走的捷徑。這是無關緊要的。但是當學習一些東西的時候,你應該先學習它的最純粹的形式,然后再學習更復雜的規則。
我早期提供的 Maybe Monad 的實現不同于其他的 Maybe,就是它沒有空置檢查。另外,我們將 Maybe 作為 Just(..) 和 Nothing() 的非嚴格意義上的結合。
等一下,如果我們沒有自動短路,那 Maybe 是怎么起作用的呢?!?這似乎就是它的全部意義。
不要擔心,我們可以從外部提供簡單的空值檢查,Maybe Monad 其他的短路行為也還是可以很好的工作的。你可以在之前做一些 someObj.something.else.entirely 屬性嵌套,但是我們可以做的更 “正確”:
function isEmpty(val) { return val === null || val === undefined; } var safeProp = curry( function safeProp(prop,obj){ if (isEmpty( obj[prop] )) return Maybe.Nothing(); return Maybe.of( obj[prop] ); } ); Maybe.of( someObj ) .chain( safeProp( "something" ) ) .chain( safeProp( "else" ) ) .chain( safeProp( "entirely" ) ) .map( console.log );
我們設計了一個用于空值檢查的 safeProp(..) 函數,并選擇了 Nothing() Monad 實例。或者把值包裝在 Just(..) 實例中(通過 Maybe.of(..))。然后我們用 chain(..) 替代 map(..),它知道如何 “展開” safeProp(..) 返回的 Monad。
當遇到空值的時候,我們得到了一連串相同的短路。只是我們把這個邏輯從 Maybe 中排除了。
不管返回哪種類型的 Monad,我們的 map(..) 和 chain(..) 方法都有不變且可預測的反饋,這就是 Monad,尤其是 Maybe Monad 的好處。這難道不酷嗎?
Humble現在我們對 Maybe 和它的作用有了更多的了解,我將會在它上面加一些小的改動 —— 我將通過設計 Maybe + Humble Monad 來添加一些轉折并且加一些詼諧的元素。從技術上來說,Humble(..) 并不是一個 Monad,而是一個產生 Maybe Monad 實例的工廠函數。
Humble 是一個使用 Maybe 來跟蹤 egoLevel 數字狀態的數據結構包裝器。具體來說,Humble(..) 只有在他們自身的水平值足夠低(少于 42)到被認為是 Humble 的時候才會執行生成的 Monad 實例;否則,它就是一個 Nothing() 空操作。這聽起來真的和 Maybe 很像!
這是一個 Maybe + Humble Monad 工廠函數:
function Humble(egoLevel) { // 接收任何大于等于 42 的數字 return !(Number( egoLevel ) >= 42) ? Maybe.of( egoLevel ) : Maybe.Nothing(); }
你可能會注意到,這個工廠函數有點像 safeProp(..),因為,它使用一個條件來決定是選擇 Maybe 的 Just(..) 還是 Nothing()。
讓我們來看一個基礎用法的例子:
var bob = Humble( 45 ); var alice = Humble( 39 ); bob.inspect(); // Nothing alice.inspect(); // Just(39)
如果 Alice 贏得了一個大獎,現在是不是在為自己感到自豪呢?
function winAward(ego) { return Humble( ego + 3 ); } alice = alice.chain( winAward ); alice.inspect(); // Nothing
Humble( 39 + 3 ) 創建了一個 chain(..) 返回的 Nothing() Monad 實例,所以現在 Alice 不再有 Humble 的資格了。
現在,我們來用一些 Monad :
var bob = Humble( 41 ); var alice = Humble( 39 ); var teamMembers = curry( function teamMembers(ego1,ego2){ console.log( `Our humble team"s egos: ${ego1} ${ego2}` ); } ); bob.map( teamMembers ).ap( alice ); // Humble 隊列:41 39
由于 teamMembers(..) 是柯里化的,bob.map(..) 的調用傳入了 bob 自身的級別(41),并且創建了一個被其余的方法包裝的 Monad 實例。在 這個 Monad 中調用的 ap(alice) 調用了 alice.map(..),并且傳遞給來自 Monad 的函數。這樣做的效果是,Monad 的值已經提供給了 teamMembers(..) 函數,并且把顯示的結果給打印了出來。
然而,如果一個 Monad 或者兩個 Monad 實際上是 Nothing() 實例(因為它們本身的水平值太高了):
var frank = Humble( 45 ); bob.map( teamMembers ).ap( frank ); frank.map( teamMembers ).ap( bob );
teamMembers(..) 永遠不會被調用(也沒有信息被打印出來),因為,frank 是一個 Nothing() 實例。這就是 Maybe monad 的作用,我們的 Humble(..) 工廠函數允許我們根據自身的水平來選擇。贊!
Humility再來一個例子來說明 Maybe + Humble 數據結構的行為:
function introduction() { console.log( "I"m just a learner like you! :)" ); } var egoChange = curry( function egoChange(amount,concept,egoLevel) { console.log( `${amount > 0 ? "Learned" : "Shared"} ${concept}.` ); return Humble( egoLevel + amount ); } ); var learn = egoChange( 3 ); var learner = Humble( 35 ); learner .chain( learn( "closures" ) ) .chain( learn( "side effects" ) ) .chain( learn( "recursion" ) ) .chain( learn( "map/reduce" ) ) .map( introduction ); // 學習閉包 // 學習副作用 // 歇息遞歸
不幸的是,學習過程看起來已經縮短了。我發現學習一大堆東西而不和別人分享,會使自我太膨脹,這對你的技術是不利的。
讓我們嘗試一個更好的方法:
var share = egoChange( -2 ); learner .chain( learn( "closures" ) ) .chain( share( "closures" ) ) .chain( learn( "side effects" ) ) .chain( share( "side effects" ) ) .chain( learn( "recursion" ) ) .chain( share( "recursion" ) ) .chain( learn( "map/reduce" ) ) .chain( share( "map/reduce" ) ) .map( introduction ); // 學習閉包 // 分享閉包 // 學習副作用 // 分享副作用 // 學習遞歸 // 分享遞歸 // 學習 map/reduce // 分享 map/reduce // 我只是一個像你一樣的學習者 :)
在學習中分享。是學習更多并且能夠學的更好的最佳方法。
總結說了這么多,那什么是 Monad ?
Monad 是一個值類型,一個接口,一個有封裝行為的對象數據結構。
但是這些定義中沒有一個是有用的。這里嘗試做一個更好的解釋:Monad 是一個用更具有聲明式的方式圍繞一個值來組織行為的方法。
和這本書中的其他部分一樣,在有用的地方使用 Monad,不要因為每個人都在函數式編程中討論他們而使用他們。Monad 不是萬金油,但它確實提供了一些有用的實用函數。
【上一章】翻譯連載 | 附錄 A:Transducing(下)-《JavaScript輕量級函數式編程》 |《你不知道的JS》姊妹篇
iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、當當開售。
iKcamp官網:https://www.ikcamp.com
訪問官網更快閱讀全部免費分享課程:
《iKcamp出品|全網最新|微信小程序|基于最新版1.0開發者工具之初中級培訓教程分享》
《iKcamp出品|基于Koa2搭建Node.js實戰項目教程》
包含:文章、視頻、源代碼
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90239.html
摘要:為了盡可能提升互通性,已經成為函數式編程庫遵循的實際標準。與輕量級函數式編程的概念相反,它以火力全開的姿態進軍的函數式編程世界。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關于譯者:這是一個流淌著滬江血液的純粹工程:認真,是 HTML 最堅實的梁柱;分享,是 CSS 里最閃耀的一瞥;總結,...
摘要:我稱之為輕量級函數式編程。序眾所周知,我是一個函數式編程迷。函數式編程有很多種定義。本書是你開啟函數式編程旅途的絕佳起點。事實上,已經有很多從頭到尾正確的方式介紹函數式編程的書了。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 譯者團隊(排名不分先后):阿希、blueken、brucecham、...
摘要:本書主要探索函數式編程的核心思想。我們在中應用的僅僅是一套基本的函數式編程概念的子集。我稱之為輕量級函數式編程。通常來說,關于函數式編程的書籍都熱衷于拓展閱讀者的知識面,并企圖覆蓋更多的知識點。,本書統稱為函數式編程者。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 譯者團隊(排名不分先后)...
摘要:所以我覺得函數式編程領域更像學者的領域。函數式編程的原則是完善的,經過了深入的研究和審查,并且可以被驗證。函數式編程是編寫可讀代碼的最有效工具之一可能還有其他。我知道很多函數式編程編程者會認為形式主義本身有助于學習。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 關于譯者:這是一個流淌著滬江血液...
摘要:我不會把嚴格的稱為輕量級函數式編程,它更像是一個頂級的技巧。實際上,我認為這是你掌握了輕量級函數式編程后可以做的最好的例證之一。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關于譯者:這是一個流淌著滬江血液的純粹工程:認真,是 HTML 最堅實的梁柱;分享,是 CSS 里最閃耀的一瞥;總結,是 ...
閱讀 680·2021-09-30 09:47
閱讀 2873·2021-09-04 16:40
閱讀 857·2019-08-30 13:18
閱讀 3452·2019-08-29 16:22
閱讀 1555·2019-08-29 12:36
閱讀 586·2019-08-29 11:11
閱讀 1478·2019-08-26 13:47
閱讀 1132·2019-08-26 13:32