JavaScript的Monad
Monad是一種設計模式,使用一系列步驟來描述計算,它們典型地使用在純函式語言中用於管理副作用,也可使用在多正規化語言中用來控制複雜性。
Monad封裝型別帶來了附加的行為,比如空值(Maybe monad)的自動傳播,或簡化非同步程式碼(Continuation monad)
為了描述一個Monad結構,需要提供以下三個元件:
1.型別構造器 :一種為基礎型別建立monadic型別的特質,比如定義Maybe<number>作為基礎型別number的monadic型別。
2.unit函式,這是封裝基礎型別的值進入一個monad,對於Maybe monad,它封裝了number型別的數值2進入Maybe<number>的型別,變成了Maybe(2)。
3.bind函式,能夠對monadic值進行鏈條化操作。
下面TypeScript程式碼展示了這些普通特性,假設M表示一個Monadic型別。
在物件導向語言中如Javascript中,unit函式能被表示為一個構造器,而bind函式被作為例項方法:
有三個Monadic法則必須遵循:
1.bind(unit(x), f) ≡ f(x)
2.bind(m, unit) ≡ m
3.bind(bind(m, f), g) ≡ bind(m, x ⇒ bind(f(x), g))
前面兩個法則是說, unit是一箇中立元素,第三條說,bind必須是可組合的associative , bind繫結順序並不重要,比如:(8 + 4) + 2 等同於 8 + (4 + 2).
下面案例需要javascript的箭頭語法支援,Firefox版本 31以上支援箭頭函式,而Chrome版本36並不支援。
下面是展示使用這個Identity monad實現計算加法:
Nothing 代表一個空值:
基本用法也類似identity monad:
與 identity monad主要區別是空值可以自動傳播,當計算任何步驟返回一個Nothing時,所有後續的計算將會忽視,返回Nothing。
下面程式碼中的alert函式就不會被執行,因為前面步驟返回了一個空值。
這種方式類似特殊值NaN (not-a-number) ,當計算中間有一個結果是NaN時,NaN值會傳播到整個計算過程:
Maybe用於保護因為null空值引起的錯誤,下面案例是返回一個登入使用者的頭像:
在一個很長的方法呼叫鏈中不檢查空值的話,如果一個返回結果是null會引起TypeError:
改進辦法是使用null檢查,但是這會使得程式碼變得冗長羅嗦,下面程式碼是正確的,但是一行變成了多行程式碼:
Maybe 提供了另外一種方式,它可以在遇到空值時停止計算:
作為陣列和generator是可被遍歷的,bind函式會作用於它們,下面案例是為每個元素前後配對計算其總數的一個懶列表:
關於更復雜的Do notation和Chained呼叫(鏈式呼叫)可見原文:
Monad封裝型別帶來了附加的行為,比如空值(Maybe monad)的自動傳播,或簡化非同步程式碼(Continuation monad)
為了描述一個Monad結構,需要提供以下三個元件:
1.型別構造器 :一種為基礎型別建立monadic型別的特質,比如定義Maybe<number>作為基礎型別number的monadic型別。
2.unit函式,這是封裝基礎型別的值進入一個monad,對於Maybe monad,它封裝了number型別的數值2進入Maybe<number>的型別,變成了Maybe(2)。
3.bind函式,能夠對monadic值進行鏈條化操作。
下面TypeScript程式碼展示了這些普通特性,假設M表示一個Monadic型別。
interface M<T> { } function unit<T>(value: T): M<T> { // ... } function bind<T, U>(instance: M<T>, transform: (value: T) => M<U>): M<U> { // ... } <p class="indent"> |
在物件導向語言中如Javascript中,unit函式能被表示為一個構造器,而bind函式被作為例項方法:
interface MStatic<T> { // constructor that wraps value new(value: T): M<T>; } interface M<T> { // bind as an instance method bind<U>(transform: (value: T) => M<U>): M<U>; } <p class="indent"> |
有三個Monadic法則必須遵循:
1.bind(unit(x), f) ≡ f(x)
2.bind(m, unit) ≡ m
3.bind(bind(m, f), g) ≡ bind(m, x ⇒ bind(f(x), g))
前面兩個法則是說, unit是一箇中立元素,第三條說,bind必須是可組合的associative , bind繫結順序並不重要,比如:(8 + 4) + 2 等同於 8 + (4 + 2).
下面案例需要javascript的箭頭語法支援,Firefox版本 31以上支援箭頭函式,而Chrome版本36並不支援。
Identity monad
這是最簡單的monad,它只是封裝一個值,Identity 構造器類似unit函式。
function Identity(value) { this.value = value; } Identity.prototype.bind = function(transform) { return transform(this.value); }; Identity.prototype.toString = function() { return 'Identity(' + this.value + ')'; }; <p class="indent"> |
下面是展示使用這個Identity monad實現計算加法:
var result = new Identity(5).bind(value => new Identity(6).bind(value2 => new Identity(value + value2))); print(result); <p class="indent"> |
Maybe Monad
類似identity monad,但是會儲存一個代表存在的值在其中。
Just構造器用於封裝值。
function Just(value) { this.value = value; } Just.prototype.bind = function(transform) { return transform(this.value); }; Just.prototype.toString = function() { return 'Just(' + this.value + ')'; }; <p class="indent"> |
Nothing 代表一個空值:
var Nothing = { bind: function() { return this; }, toString: function() { return 'Nothing'; } }; <p class="indent"> |
基本用法也類似identity monad:
var result = new Just(5).bind(value => new Just(6).bind(value2 => new Just(value + value2))); print(result); <p class="indent"> |
與 identity monad主要區別是空值可以自動傳播,當計算任何步驟返回一個Nothing時,所有後續的計算將會忽視,返回Nothing。
下面程式碼中的alert函式就不會被執行,因為前面步驟返回了一個空值。
var result = new Just(5).bind(value => Nothing.bind(value2 => new Just(value + alert(value2)))); print(result); <p class="indent"> |
這種方式類似特殊值NaN (not-a-number) ,當計算中間有一個結果是NaN時,NaN值會傳播到整個計算過程:
var result = 5 + 6 * NaN; print(result); <p class="indent"> |
Maybe用於保護因為null空值引起的錯誤,下面案例是返回一個登入使用者的頭像:
function getUser() { return { getAvatar: function() { return null; // no avatar } }; } <p class="indent"> |
在一個很長的方法呼叫鏈中不檢查空值的話,如果一個返回結果是null會引起TypeError:
try { var url = getUser().getAvatar().url; print(url); // this never happens } catch (e) { print('Error: ' + e); } <p class="indent"> |
改進辦法是使用null檢查,但是這會使得程式碼變得冗長羅嗦,下面程式碼是正確的,但是一行變成了多行程式碼:
var url; var user = getUser(); if (user !== null) { var avatar = user.getAvatar(); if (avatar !== null) { url = avatar.url; } } print(url); <p class="indent"> |
Maybe 提供了另外一種方式,它可以在遇到空值時停止計算:
function getUser() { return new Just({ getAvatar: function() { return Nothing; // no avatar } }); } var url = getUser() .bind(user => user.getAvatar()) .bind(avatar => avatar.url); if (url instanceof Just) { print('URL has value: ' + url.value); } else { print('URL is empty.'); } <p class="indent"> |
List monad
List列表monad表示一個懶計算的值的集合列表。
這個monad會unit函式獲取一個輸入值,返回一個yield在這個值的generator(yield 是用於暫停,然後resume再次開始一個generator 函式 ) ,bind函式會應用transform函式到列表集合中每個元素,然後yield住結果中的所有元素。
function* unit(value) { yield value; } function* bind(list, transform) { for (var item of list) { yield* transform(item); } } <p class="indent"> |
作為陣列和generator是可被遍歷的,bind函式會作用於它們,下面案例是為每個元素前後配對計算其總數的一個懶列表:
var result = bind([0, 1, 2], function (element) { return bind([0, 1, 2], function* (element2) { yield element + element2; }); }); for (var item of result) { print(item); } <p class="indent"> |
Continuation monad
Continuation monad是用於實現非同步任務,很幸運ES6沒有必要這樣實現了,因為Promise物件實際就是這種monad的實現.
1.Promise.resolve(value) 封裝一個值,返回一個promise (一個 unit 函式).
2.Promise.prototype.then(onFullfill: value => Promise) 獲取一個輸入引數,一個函式將這個引數值轉為不同的promise 然後返回一個promise (也就是bind 函式).
// Promise.resolve(value) will serve as the Unit function // Promise.prototype.then will serve as the Bind function Native promises var result = Promise.resolve(5).then(function(value) { return Promise.resolve(6).then(function(value2) { return value + value2; }); }); result.then(function(value) { print(value); }); <p class="indent"> |
關於更復雜的Do notation和Chained呼叫(鏈式呼叫)可見原文:
Monads in JavaScript — Curiosity driven
[該貼被banq於2015-07-25 14:58修改過]
相關文章
- promise is a monad?Promise
- 圖解 Monad圖解
- Haskell學習-monadHaskell
- Promise是Monad嗎?Promise
- Monad和Monoid的定義Mono
- 談談我對Monad的理解
- Java的Monad和懶賦值Java賦值
- [譯]JavaScript 讓 Monad 更簡單(軟體編寫)(第十一部分)JavaScript
- 《Haskell趣學指南》筆記之 MonadHaskell筆記
- 轉評:你造promise就是monad嗎Promise
- Typescript版圖解Functor , Applicative 和 MonadTypeScript圖解APP
- Kotlin 版圖解 Functor、Applicative 與 MonadKotlin圖解APP
- 翻譯連載 | 附錄 B: 謙虛的 Monad-《JavaScript輕量級函數語言程式設計》 |《你不知道的JS》姊妹篇JavaScript函數程式設計JS
- async/await 之於 Promise,正如 do 之於 monad(譯文)AIPromise
- 完整解釋 Monad -- 程式設計師範疇論入門程式設計師
- 函數語言程式設計-將Monad(單子)融入Swift函數程式設計Swift
- Java中函數語言程式設計Monad概念介紹Java函數程式設計
- 函數語言程式設計 - Swift中的Functor(函子)、Monad(單子)、Applicative函數程式設計SwiftAPP
- 從回撥地獄到自函子上的么半群:解密熟悉又陌生的 Monad解密
- javascript的thisJavaScript
- javascript,還是javascript的問題JavaScript
- JavaScript的this的指向JavaScript
- JavaScript中的thisJavaScript
- javascript的this原理JavaScript
- JavaScript 的 this 原理JavaScript
- javascript 下的 thisJavaScript
- JavaScript 中的 this !JavaScript
- JavaScript 中的 !!JavaScript
- 高效的 JavaScriptJavaScript
- Javascript 的 this 用法JavaScript
- JavaScript 是如何工作的:JavaScript 的記憶體模型JavaScript記憶體模型
- JavaScript JavaScript與XML——“XPath”的注意要點JavaScriptXML
- javascript的match方法JavaScript
- JavaScript 中的 exportJavaScriptExport
- 理解 JavaScript 中的 thisJavaScript
- JavaScript 中 This 的指向JavaScript
- 解析javascript中的thisJavaScript
- JavaScript中的 FunctionJavaScriptFunction