[譯]淺入淺出Monads
大多數關于 monad 的教程都和老太太的裹腳布一樣,又臭、又長,說不清、道不明。當然我也不偉大,沒法保證我寫的一定更明了,更生動,甚至更屌?不過我至少可以確定,我這篇更簡潔。浪費不了你多少時間的!
廢話不多說,先看下面這個對象 Foo 。她就是個 monad 。你必定會吃驚道:我擦,這是什么意思?不要急,故事要從頭說,我們還是先來分析下 Foo 到底是怎么干活的:
function Foo(value) {
this.get = ()=> value;
this.map = fn => {
let result = fn(value);
return new Foo(result);
};
}
Foo 接受了一個 value ,而且一直都沒改變她的值。 Foo 里提供了一個 get 方法使得外部調用者可以獲取 value ,還有一個牛逼的方法叫 map 。 map 接受另一個函數( handler )作為參數,然后用接受的這個新函數 handler 處理 value ,將結果再次傳給 Foo ,最后將實例化的新 Foo 對象返回。
因為 map 返回一個 Foo 的實例,于是 map 的方法是可以被鏈式調用的:
let one = new Foo(1);
let two = one.map(x => x + 7).map(x => x / 2).map(x => x - 2);
two.get() === 2;
鏈式調用high不 high?她允許我們可以按照期望,對 x 執行順序操作,這種更“自然”的風格絕對比下面這種瘋狂嵌套的風格要好:
//嵌套組合的方式長這個樣子,
//我們必須從右向左讀,才能得出結論
//而且你說實話,這風格,你喜歡么?
let two = minusTwo(divideByTwo(addSeven(1)));
而且每一個步驟里處理 value 到下一個 Foo 實例的邏輯我們都可以抽離出去。
再來看看另一個 monad ,我們姑且稱之為 Bar 吧:
function Bar(value) {
this.get = ()=> value;
this.map = fn => {
let result = fn(value);
console.log(result);
return new Bar(result);
};
}
如果這時候我有一系列操作想順序作用在 value 上,而且還要在每次變化時打印出來新的 value ,就可以利用 Bar 把下面這種原始的,二逼的代碼:
let stepOne = something(1);
console.log(stepOne);
let stepTwo = somethingElse(stepOne);
console.log(stepTwo);
let stepThree = somethingDifferent(stepTwo);
console.log(stepThree);
重構成下面這種優雅的,高端的樣子了:
new Bar(1)
.map(something) // console >> logs new value
.map(somethingElse) // console >> logs new value
.map(somethingDifferent); // console >> logs new value
現在你應該懂什么是 monads 了 。我完成諾言了哦! Monads 可以粗略的歸納出下面這些規則:
-
monad 總會包含一個值
-
monad 有一個 map 方法,而且該方法會接受一個函數( handler )作為參數
-
map 通過上一步提到的 handler 處理 value (還可能有些其他邏輯),并獲取其結果
-
map 最后返回一個 new [Monad] ,以完成鏈式調用
目前能想到的就這些了。如果上述的例子你都理解了,那你就懂什么是 Monads 了。如果你還再等什么黑魔法或者驚奇算法,那抱歉了,還真沒有!
理論上,我們任意修改 map 的實現,任何可以在各步驟 handler 之間的邏輯都行 - 例如:決定傳什么內容到下一步,或者對上一步 handler 處理的結果做點兒什么。
空值檢查就是個不錯的例子:
function Maybe(value) {
this.get = ()=> value;
this.map = fn => {
if (value === null) {
return new Baz(null);
} else {
return new Baz(fn(value));
}
};
}
這個實現里, map 只在 value 為合法值(非空)時,傳入 handler 。否則就只返回一個自身的copy。利用上面的非空檢查的 Monad 函數 Maybe ,我們可以把下面這個冗長矬的代碼:
let connection = getConnection();
let user = connection ? connection.getUser() : null;
let address = user ? user.getAddress() : null;
let zipCode = address ? address.getZip() : null;
重構成這個樣子:
let zipCode =
new Maybe(getConnection())
.map(c => c.getUser())
.map(u => u.getAddress())
.map(a => a.getZip());
//最后得到的要么是真正的zipCode(每一步都正確處理)
//要么就是個null
zipCode.get();
希望今天的說教已經說明了 monads 和她的 map 方法為什么這么牛逼了!關于這點,我倒是不懷疑你也能自己想出來^^
還有其他很多種 monads ,都有不同的用法和用途。但不論怎么變化,她們也都和 Foo 、 Bar 一樣遵守上面提到的規則。
掌握了這些技巧,你基本就可以裝做一個會寫函數式的“牛人”了!
原文地址: Monads Explained Quickly