楔子

程式碼是拿來使用的,今天就來分享一些小工具來幫助寫 code ,這些東西算是函數式編程的入門吧,用久了才會比較有感,今天就當來練練手。

先上個常用的 tool box

const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x)
const I = x => x
const Box = x =>
({
  chain: f => f(x),
  map: f => Box(f(x)),
  fold: f => f(x),
  inspect: () => `Box(${x})`
})

const Right = x =>
({
  chain : f => f(x),
  map : f => Right(f(x)),
  fold : (f, g) => g(x),
  inspect : () => `Right(${x})`
})

const Left = x =>
({
  chain : f => Left(x),
  map : f => Left(x),
  fold : (f, g) => f(x),
  inspect : () => `Left(${x})`
})

const fromNullable = x =>
  (x === null || x === undefined || x.length !== 0) ? Left(null) : Right(x)

const tryCatch = f => {
 try {
   return Right(f())
 } catch (e) {
   return Left(e)
 }
}

Null vs undefined

在 javascript 的特性中,最討厭的就是 undefined 了..

在處理資料的時候,常常都要檢查錯誤

(如果沒有檢查習慣的小夥伴,應該就每天活在 debug 的煉獄中了吧)

很常的習慣就大概會像這樣的 code

// 假設隨便取個數字當 3 時就給 undefined 或 null
function getUndefined(x) {
  return x === 3 ? undefined : x
}

let checkData = getUndefined(3)
if (checkData === undefined) {
  console.log('yo.. x 是 undefined', )
}
// 那如果很多檢查條件呢? null, []
if (checkData === undefined || checkData === null || checkData.length === 0) {
  console.log('yo.. x 是 undefined, null, []')
}
// 注意一下如果這個會噴錯哦? why?
// if (checkData.length === 0 || checkData === undefined || checkData === null || ) {}

所以在很多 coding 的過程中都一直在檢查資料的格式是否符合,有時少檢查就會噴錯了,

但如果運氣好測式資料沒注意,但結果噴錯就往往造成程式的中斷就有點難搞了。

好一點的就使用一個檢查的 function 丟進去來處理

function checkData (x) {
  // 設立一些條件
  return (x === null || x === undefined || x.length === 0) ? false : true
}

let data1 = getData(x)
let checkData1 = checkData(x)
if (checkData1) {
  // do data1 作用
}

換個思維

如果我們把「過濾條件」和「噴錯」(new Error)都視為一個 Box 內的資料,

如果可以做值的運算就叫 Right,不可以運算的就叫 Left,

再運用之前介紹的 fmap 的觀念(就是丟一個 function 去處理 Box 內的值),

那其實是不是感覺省事很多,就不用一堆 if-else

再回首

再看一下最上面的小工具函數,大概就可以有這樣的示範 code

let data1 = Box(3).fmap(x => x + 1) // Right(4)
let data2 = Box(undefined).fmap(x => x + 1) // Left(null)
// 甚至 error
let data3 = tryCatch(() => null.length).fmap(x => x + 1) //?? Left(e)
let data4 = fromNullable(null).fmap(x => x + '!!') // Left(null)