# Monad
# Either Monad
- 一條是正確的路(Right),運算過過程一切順利
- 另一條(Left)是只要某一處的運算出現錯誤,就會跳過,直接輸出失敗的結果
const Right = (x) => ({
chain: (f) => f(x),
map: (f) => Right(f(x)),
fold: (f, g) => g(x), // 執行 g 函式
toString: `Right(${x})`,
});
const Left = (x) => ({
chain: (f) => Left(x),
map: (f) => Left(x),
fold: (f, g) => f(x), // 執行 f 函式
toString: `Left(${x})`,
});
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
另外,我們還可以自訂處理錯誤的函式,如:
# fromNullable
判斷參數的條件是否為 null
或 undefined
,不是則回傳 Right Side,是則返回 Left Side。
const fromNullable = (x) => (x != null ? Right(x) : Left(null));
1
補充
null
和 undefined
在做 !=
比較運算時,會都轉為布林,即 false != false
,得到 false
結果。
如果做 !==
比較運算時,會加入 typeof
的判斷,即 typeof null !== typeof undefined && Boolean(null) !== Boolean(undefined)
,得到 true && false
的 false
結果。
# tryCatch
判斷傳入參數的執行結果是否正確,是則回傳 Right Side,不是則返回 Left Side。
const tryCatch = (f) => {
try {
return Right(f());
} catch (e) {
return Left(e);
}
};
1
2
3
4
5
6
7
2
3
4
5
6
7
# 如何除錯
const log = (msg) => (x) => {
console.log(msg, '--->', x, '<---');
return x;
};
1
2
3
4
2
3
4
# 練習
重構取得 street.name 並使用 Either 而不是嵌套的 if
const street = (user) => { const address = user.address; if (address) { return address.street; } else { return 'no street'; } };
1
2
3
4
5
6
7
8
9重構:
const street = (user) => fromNullable(user.address) .map((address) => address.street) .fold( () => 'no street', (streetName) => streetName );
1
2
3
4
5
6
7測試:
street({}); // no street street({ address: { street: { name: 'Willow' } } }); // {name: "Willow"}
1
2重構取得 parse url 的結果,並使用 Either 而不是 try / catch
const DB_REGEX = /postgres:\/\/([^:]+):([^@]+)@.*?\/(.+)$/i; const parseDbUrl = (cfg) => { try { const c = JSON.parse(cfg); // throws if it can't parse return c.url.match(DB_REGEX); } catch (e) { return null; } };
1
2
3
4
5
6
7
8
9
10重構:
const parseDbUrl = (cfg) => tryCatch(() => JSON.parse(cfg)) .map((c) => c.url.match(DB_REGEX)) .fold( () => null, (result) => result ); parseDbUrl('{"url": "postgres://sally:muppets@localhost:5432/mydb"}')[1]; // sally parseDbUrl(); // null
1
2
3
4
5
6
7
8
9
10使用上述任一功能,重構 startApp
const startApp = (cfg) => { const parsed = parseDbUrl(cfg); if (parsed) { const [_, user, password, db] = parsed; return `starting ${db}, ${user}, ${password}`; } else { return "can't get config"; } };
1
2
3
4
5
6
7
8
9
10重構:
const startApp = (x) => fromNullable(parseDbUrl(x)).fold( () => "can't get config", ([_, user, password, db]) => `starting ${db}, ${user}, ${password}` ); startApp('{"url": "postgres://sally:muppets@localhost:5432/mydb"}'); // starting mydb, sally, muppets startApp(); // can't get config
1
2
3
4
5
6
7
8