點燈坊

學而時習之,不亦悅乎

使用 map() 改變 Maybe 內的值

Sam Xiao's Avatar 2019-05-23

當值被包進 Maybe 後,該如何改變 Maybe 內的值呢 ? Maybe 自帶 map(),將原來 Function 傳進 map() 即可。

Version

macOS Mojave 10.14.5
VS Code 1.34.0
Quokka 1.0.216
Ramda 0.26.1
Crocks 0.11.1

Imperative

import { inc, multiply } from 'ramda';

// fn :: Number -> Number
let fn = n => {
  let incremented = inc(n);
  return multiply(2, incremented);
};

console.log(fn(1));

fn() 要先經過 inc(),然後再將結果傳入 multiply(2)

Imperative 會先用變數保存 inc() 計算完結果,再將變數傳入 multiply()

maybe000

Functional

import { pipe, inc, multiply } from 'ramda';

// fn :: Number -> Number
let fn = pipe(
  inc,
  multiply(2)
);

console.log(fn(1));

Functional 會使用 pipe()inc()multiply() 加以組合。

但 ECMAScript 是 dynamic type language,雖然我們希望是 Number -> Number,但若傳入 string,結果就是 NaN,並不是我們預期結果。

maybe001

Maybe

import { inc, multiply } from 'ramda';
import { safe, isNumber } from 'crocks';

// fn :: Number -> Number
let fn = n => safe(isNumber)(n)
  .map(inc) 
  .map(multiply(2))
  .option(0);

console.log(fn(1));

使用 safe(isNumber)n 包成 Maybe 後,必須使用其自帶的 map() 才能改變 Maybe 內的值。

由於需經過 inc()multiply() 運算,因此使用了兩次 map()

最後再使用 option()Maybe 轉回 number。

map()
Maybe a ~> (a -> b) -> Maybe b
傳入 function 直接修改 Maybe 內的值

Maybe a:data 為 Maybe

(a -> b):傳入 a -> b 的 function

Maybe b:將 Maybe 內的 a 改成 b

maybe002

import { pipe, inc, multiply } from 'ramda';
import { safe, isNumber } from 'crocks';

// fn :: Number -> Number
let fn = pipe(
  inc,
  multiply(2)
);

// fn2 :: Number -> Number
let fn2 = n => safe(isNumber)(n)
  .map(fn)
  .option(0);

console.log(fn2(1));

使用兩次 map() 雖然可行,但原來的 function 已經被打散了。

事實上我們可以維持原本的 function 不變,只需一次 map() 即可。

maybe003

Conclusion

  • 不用擔心使用 Maybe 後,原本的 function 需要打散後再各自 map(),事實上可將整個 function 傳進 map(),維持原本的 function 完全不用修改

Reference

Andy Van Slaars, Unwrap Values from a Maybe
Crocks, Maybe
Crocks, map()